├── lib
├── midinous
│ ├── debug.rb
│ ├── extensions.rb
│ ├── key_bindings.rb
│ ├── proc_midi.rb
│ ├── constants.rb
│ ├── logic.rb
│ ├── init.rb
│ ├── canvas.rb
│ ├── style
│ │ ├── ui.rb
│ │ └── midinous.glade
│ └── points.rb
├── midinous.rb
├── saves
│ └── sample.nous
└── doc
│ ├── Hotkeys.txt
│ └── Notes and Scales.txt
├── README.md
└── COPYING.L
/lib/midinous/debug.rb:
--------------------------------------------------------------------------------
1 | trace = TracePoint.new(:call) do |tp|
2 | puts "#{tp.defined_class}##{tp.method_id} got called (#{tp.path}:#{tp.lineno})"
3 | end
4 | trace.enable
5 |
6 | #IO.binwrite("out.txt","")
7 | #IO.binread("out.txt")
8 | #$file = File.open("out.txt", 'a')
--------------------------------------------------------------------------------
/lib/midinous.rb:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2019 James "Nornec" Ratliff
2 | #
3 | # Midinous is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # Midinous is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with Midinous. If not, see .
15 |
16 | require "unimidi"
17 | require "midi-eye"
18 | require "gtk3"
19 | require "midinous/init"
20 |
21 | Gtk.main
--------------------------------------------------------------------------------
/lib/saves/sample.nous:
--------------------------------------------------------------------------------
1 | 0<~>[1995.0, 2135.0]<~>60<~>100<~>1<~>1<~>#4c4c4c<~>0<~>["robin", "split", "portal", "random"]<~>true<~>false<~>horz<~>[1]<~>[3]
2 | 1<~>[2065.0, 2135.0]<~>+1<~>100<~>1<~>1<~>#4c4c4c<~>0<~>["robin", "split", "portal", "random"]<~>false<~>true<~>horz<~>[2]<~>[0, 6]
3 | 2<~>[2065.0, 2205.0]<~>-3<~>100<~>1<~>1<~>#4c4c4c<~>0<~>["robin", "split", "portal", "random"]<~>false<~>true<~>horz<~>[5, 3]<~>[1]
4 | 3<~>[2065.0, 2275.0]<~>+3<~>100<~>1<~>1<~>#4c4c4c<~>0<~>["portal", "random", "robin", "split"]<~>false<~>true<~>horz<~>[0]<~>[2, 4]
5 | 4<~>[2135.0, 2275.0]<~>+7<~>100<~>1<~>1<~>#4c4c4c<~>0<~>["robin", "split", "portal", "random"]<~>false<~>true<~>horz<~>[3]<~>[5]
6 | 5<~>[2135.0, 2205.0]<~>-1<~>100<~>1<~>1<~>#4c4c4c<~>0<~>["random", "robin", "split", "portal"]<~>false<~>true<~>horz<~>[6, 4]<~>[2]
7 | 6<~>[2135.0, 2135.0]<~>-4<~>100<~>1<~>1<~>#4c4c4c<~>0<~>["robin", "split", "portal", "random"]<~>false<~>true<~>horz<~>[1]<~>[5]
8 | 80.0<~>Dorian<~>67<~>4<~>4
9 |
--------------------------------------------------------------------------------
/lib/doc/Hotkeys.txt:
--------------------------------------------------------------------------------
1 | [ -- decrement grid size/change time signature divisor -1
2 | ] -- increment grid size/ change time signature divisor +1
3 | { -- decrement time signature denominator (slows down play) - min 2
4 | } -- increment time signature denominator (speeds up play) - max 16
5 | delete -- deletes selected point(s)
6 | home -- deletes paths entering selected point(s)
7 | end -- deletes paths leaving selected point(s)
8 | Page Up -- change path direction
9 | Page Down -- change path direction
10 | A -- Make starting point (need at least 1 to begin play)
11 | T -- Built path - build a path between one or more points
12 | Q -- Select Tool - Box select or single select points
13 | W -- Point Place Tool - Places a point
14 | E -- Move Tool - moves selected points based on a stencil
15 | R -- Path Tool - Select source point, select target points, push build path or hit 'T'
16 | < and > -- Cycle Play Mode - between robin, portal (random, split, if more than one path is leaving a point)
17 | , and . -- Cycle starting path if more than one path is leaving a point
18 | + and - -- add or subtract 1 from the currently selected points' note
19 | Num 6 -- play
20 | Num 5 -- stop
21 | space -- toggle play/stop
22 | Ctrl+A -- select all
23 | Ctrl+C -- copy selected points
24 | Ctrl+X -- cut selected points
25 | Ctrl+V -- paste copied/cut points
26 | Ctrl+O -- Open
27 | Ctrl+S -- Save
28 | Shift+Ctrl+S -- Save As
29 | Ctrl+N -- New
30 | Ctrl+Q -- Quit
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Please note, if you are looking for the .NET version of this program, go to https://midinous.com . I am no longer maintaining this repository, as my efforts have fully shifted to the .NET version. Thank you for your interest!
2 |
3 |
4 | # Open Midinous: Grid-based Generative MIDI Software, written in Ruby
5 |
6 | Open Midinous combines generative features with a grid-based GUI to offer a supplemental and unique compositional experience
7 |
8 | ## Requirements
9 |
10 | * [GTK3](http://www.gtk.org/) 3.3.6 or later (installed as dependency automatically)
11 | * Open Midinous is designed to run on 64-bit architectures
12 | * You must have 64-bit Ruby version 2.5.0 - 2.5.5 installed with the devkit and MSYS2
13 | * It is recommended you use a virtual MIDI cable program for use of Midinous on Windows
14 |
15 | ## Install
16 |
17 | gem install midinous
18 |
19 | ## Tutorials, examples:
20 |
21 | * doc/Hotkeys.txt: Useful hotkeys used within the program
22 | * saves/sample.nous: Example Midinous project having every basic feature. Experiment from this starting point!
23 | * Tutorial video: https://www.youtube.com/watch?v=l20CNUEJumw
24 |
25 | ## License
26 |
27 | Copyright (C) 2024 Jae "Nornec" Rin
28 |
29 | Midinous is free software: you can redistribute it and/or modify
30 | it under the terms of the GNU General Public License as published by
31 | the Free Software Foundation, either version 3 of the License, or
32 | (at your option) any later version.
33 |
34 | Please note that MIDINOUS and the MN logo are registered trademarks of Jae "Nornec" Rin.
35 |
36 | ## Join the Discord!
37 |
38 | * https://discordapp.com/invite/2G5GNGk
39 |
40 | ## Project Websites
41 |
42 | * https://midinous.com
43 | * https://nornec.bandcamp.com
44 | * https://www.youtube.com/channel/UCVUr0uNVwrfvl3IKF-f7bwQ (Midinous .NET Version YouTube Channel)
45 | * https://youtube.com/nornec (Artist channel)
46 | * https://rubygems.org/gems/midinous
47 | * https://github.com/Nornec/Midinous
48 |
49 | ## Donate
50 |
51 | * If you enjoy Open Midinous, please consider donating by purchasing my music (name your price):
52 | * https://nornec.bandcamp.com
53 |
--------------------------------------------------------------------------------
/lib/midinous/extensions.rb:
--------------------------------------------------------------------------------
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 .
17 |
18 | module UniMIDI
19 | class Loader
20 | class << self
21 | def clear_devices
22 | @devices = nil
23 | end
24 | end
25 | end
26 | end
27 |
28 | module MIDIWinMM
29 | module Map
30 | def reattach_funcs
31 | attach_function :midiInOpen, [:pointer, :uint, :input_callback, :DWORD_PTR, :DWORD], :MMRESULT
32 | end
33 | end
34 |
35 | #References MIDIWinMM\input.rb
36 | class Input
37 | def enable(options = {}, &block)
38 | init_input_buffer
39 | handle_ptr = FFI::MemoryPointer.new(FFI.type_size(:int))
40 | initialize_local_buffer
41 | @event_callback = get_event_callback
42 | Map.winmm_func(:midiInOpen, handle_ptr, @id, @event_callback, 0, Device::WinmmCallbackFlag)
43 |
44 | @handle = handle_ptr.read_int
45 |
46 | #Map.winmm_func(:midiInPrepareHeader, @handle, @header.pointer, @header.size)
47 | #Map.winmm_func(:midiInAddBuffer, @handle, @header.pointer, @header.size)
48 | Map.winmm_func(:midiInStart, @handle)
49 |
50 | @enabled = true
51 |
52 | unless block.nil?
53 | begin
54 | yield(self)
55 | ensure
56 | close
57 | end
58 | else
59 | self
60 | end
61 | end
62 | alias_method :start, :enable
63 | alias_method :open, :enable
64 | end
65 |
66 |
67 | end
68 |
69 | class GuiListener < MIDIEye::Listener
70 |
71 | GUI_LISTEN_INTERVAL = 1.0 / 10
72 |
73 | def gui_listen_loop
74 | loop do
75 | poll
76 | @event.trigger_enqueued
77 | sleep(GUI_LISTEN_INTERVAL)
78 | end
79 | end
80 |
81 | def gui_listen
82 | @listener = Thread.new do
83 | begin
84 | gui_listen_loop
85 | rescue Exception => exception
86 | Thread.main.raise(exception)
87 | end
88 | end
89 | @listener.abort_on_exception = true
90 | true
91 | end
92 | end
--------------------------------------------------------------------------------
/lib/doc/Notes and Scales.txt:
--------------------------------------------------------------------------------
1 | Note Octave
2 | -2 -1 0 1 2 3 4 5 6 7 8
3 | C 0 12 24 36 48 60 72 84 96 108 120
4 | C# 1 13 25 37 49 61 73 85 97 109 121
5 | D 2 14 26 38 50 62 74 86 98 110 122
6 | D# 3 15 27 39 51 63 75 87 99 111 123
7 | E 4 16 28 40 52 64 76 88 100 112 124
8 | F 5 17 29 41 53 65 77 89 101 113 125
9 | F# 6 18 30 42 54 66 78 90 102 114 126
10 | G 7 19 31 43 55 67 79 91 103 115 127
11 | G# 8 20 32 44 56 68 80 92 104 116 -
12 | A 9 21 33 45 57 69 81 93 105 117 -
13 | A# 10 22 34 46 58 70 82 94 106 118 -
14 | B 11 23 35 47 59 71 83 95 107 119 -
15 |
16 | Note Octave
17 | -2 -1 0 1 2 3 4 5 6 7 8
18 | C 00 0C 18 24 30 3C 48 54 60 6C 78
19 | C# 01 0D 19 25 31 3D 49 55 61 6D 79
20 | D 02 0E 1A 26 32 3E 4A 56 62 6E 7A
21 | D# 03 0F 1B 27 33 3F 4B 57 63 6F 7B
22 | E 04 10 1C 28 34 40 4C 58 64 70 7C
23 | F 05 11 1D 29 35 41 4D 59 65 71 7D
24 | F# 06 12 1E 2A 36 42 4E 5A 66 72 7E
25 | G 07 13 1F 2B 37 43 4F 5B 67 73 7F
26 | G# 08 14 20 2C 38 44 50 5C 68 74 -
27 | A 09 15 21 2D 39 45 51 5D 69 75 -
28 | A# 0A 16 22 2E 3A 46 52 5E 6A 76 -
29 | B 0B 17 23 2F 3B 47 53 5F 6B 77 -
30 |
31 | W = whole tone, H = Half tone
32 | Scales and their relative intervals
33 | Aeolian R-W-H-W-W-H-W-W
34 | Altered R-H-W-H-W-W-W-W
35 | Augmented R-3H-H-3H-H-3H-H
36 | Blues R-3H-W-H-H-3H-W
37 | Chromatic R-H-H-H-H-H-H-H-H-H-H-H-H
38 | Dorian R-W-H-W-W-W-H-W
39 | Flamenco R-H-3H-H-W-H-3H-H
40 | Half Diminished R-W-H-W-H-W-W-W
41 | Harmonic major R-W-W-H-W-H-3H-H
42 | Harmonic minor R-W-H-W-W-H-3H-H
43 | Hirajoshi R-2W-W-H-2W-H
44 | Hungarian R-W-H-3H-H-H-3H-H
45 | Insen R-H-2W-W-2W-W
46 | Ionian R-W-W-H-W-W-W-H
47 | Iwato R-H-2W-H-2W-W
48 | Locrian R-H-W-W-H-W-W-W
49 | Locrian Major R-W-W-H-H-W-W-W
50 | Lydian R-W-W-W-H-W-W-H
51 | Lydian Augmented R-W-W-W-W-H-W-H
52 | Pentatonic Major R-W-W-3H-W-3H
53 | Pentatonic Minor R-3H-W-W-3H-W
54 | Melodic minor R-W-H-W-W-W-W-H
55 | Mixolydian R-W-W-H-W-W-H-W
56 | Octatonic Whole R-W-H-W-H-W-H-W-H
57 | Octatonic Half R-H-W-H-W-H-W-H-W
58 | Persian R-H-3H-H-H-W-3H-H
59 | Phrygian R-H-W-W-W-H-W-W
60 | Prometheus R-W-W-W-3H-H-W
61 | Tritone R-H-3H-W-H-3H-W
62 | Two-semitone Tritone R-H-H-4H-H-H
63 | Ukrainian Dorian R-W-H-3H-H-W-H-W
64 | Whole Tone R-W-W-W-W-W-W
65 |
--------------------------------------------------------------------------------
/lib/midinous/key_bindings.rb:
--------------------------------------------------------------------------------
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 .
17 |
18 | module Key_Bindings
19 | def route_key(event)
20 | #puts event.keyval
21 | unless !UI::logic_controls.focus? #Key bindings for the main canvas screen
22 | case event.keyval
23 | when 113 # Q
24 | UI::main_tool_1.signal_emit("keybinding-event") if CC.dragging == false
25 | when 119 # W
26 | UI::main_tool_2.signal_emit("keybinding-event") if CC.dragging == false
27 | when 101, 102 # E or F (colemak)
28 | UI::main_tool_3.signal_emit("keybinding-event") if CC.dragging == false
29 | when 114, 112 # R or P (colemak)
30 | UI::main_tool_4.signal_emit("keybinding-event") if CC.dragging == false
31 | when 65535 # del
32 | UI::canvas.signal_emit("delete-selected")
33 | when 109 # M
34 | UI::canvas.signal_emit("mute-toggle")
35 | when 116 # T
36 | UI::path_builder.signal_emit("keybinding-event")
37 | when 93 # ]
38 | UI::canvas.signal_emit("beat-up")
39 | when 91 # [
40 | UI::canvas.signal_emit("beat-dn")
41 | when 125 # }
42 | UI::canvas.signal_emit("beat-note-up")
43 | when 123 # {
44 | UI::canvas.signal_emit("beat-note-dn")
45 | when 60
46 | UI::canvas.signal_emit("cycle-play-mode-bck")
47 | when 62
48 | UI::canvas.signal_emit("cycle-play-mode-fwd")
49 | when 44 # ,
50 | UI::canvas.signal_emit('path-rotate-bck')
51 | when 46 # .
52 | UI::canvas.signal_emit('path-rotate-fwd')
53 | when 97
54 | UI::canvas.signal_emit("set-start")
55 | when 65367 # end
56 | UI::canvas.signal_emit("del-path-to")
57 | when 65360 # home
58 | UI::canvas.signal_emit("del-path-from")
59 | when 65365 # page up
60 | UI::canvas.signal_emit("set-path-mode-h")
61 | when 65366 # page down
62 | UI::canvas.signal_emit("set-path-mode-v")
63 | when 65451, 43 # +
64 | UI::canvas.signal_emit("note-inc-up")
65 | when 65453, 95 # -
66 | UI::canvas.signal_emit("note-inc-dn")
67 | end
68 | if (event.keyval == 65462 || event.keyval == 32) && UI::play.sensitive? == true
69 | UI::play.signal_emit("keybinding-event")
70 | elsif (event.keyval == 65461 || event.keyval == 32) && UI::stop.sensitive? == true
71 | UI::stop.signal_emit("keybinding-event")
72 | end
73 | end
74 |
75 | unless !UI::prop_mod.focus? #Key bindings for the property modification text entry area
76 | if event.keyval == 65293 && UI::prop_mod_button.sensitive? == true #Enter key
77 | UI::prop_mod.signal_emit("keybinding-event")
78 | end
79 | end
80 |
81 | end
82 |
83 | end
--------------------------------------------------------------------------------
/lib/midinous/proc_midi.rb:
--------------------------------------------------------------------------------
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 .
17 |
18 | class Proc_Midi
19 | attr_accessor :out_list, :in_list, :in_chan
20 | attr_reader :out_id, :in_id, :in, :midi_in
21 |
22 | def initialize(oid,iid)
23 | @out_list = []
24 | @in_list = []
25 | @in_chan = 1
26 |
27 | @out_id = oid
28 | @out_list = UniMIDI::Output.all
29 | @out = UniMIDI::Output.use(@out_id)
30 |
31 | @in_id = iid
32 | @in_list = UniMIDI::Input.all
33 | unless @in_list.length <= 1
34 | @in = UniMIDI::Input.use(@in_id)
35 | set_listener
36 | end
37 | end
38 | def regenerate
39 | @midi_in.close unless @midi_in.nil?
40 | @in_list.each do |i|
41 | #i.clear_buffer
42 | i.close
43 | end
44 | @out_list.each {|o| o.close}
45 | UniMIDI::Input.all.each {|i| i = nil}
46 | UniMIDI::Output.all.each {|o| o = nil}
47 |
48 | @in = nil
49 | @out = nil
50 |
51 | @in_list = []
52 | @out_list = []
53 |
54 | UniMIDI::Loader.clear_devices
55 |
56 | @in_list = UniMIDI::Input.all
57 | @out_list = UniMIDI::Output.all
58 |
59 | UI.set_device(0,"i")
60 | UI.set_device(0,"o")
61 |
62 | set_listener unless @in_list.length <= 1
63 | end
64 |
65 | #Restart the listener
66 | def set_listener
67 | @midi_in = GuiListener.new(@in)
68 | @midi_in.listen_for(:class => [MIDIMessage::NoteOn]) do |e|
69 | Pl.set_note_via_devc(e[:message].note.clamp(0,127)) if e[:message].velocity <= 127
70 | end
71 | @midi_in.gui_listen
72 | end
73 |
74 | #Select the output device
75 | def sel_out(id)
76 | @out = UniMIDI::Output.all[@out_id].close
77 | @out = UniMIDI::Output.use(id)
78 | @out_id = id
79 | end
80 |
81 | #Select the input device
82 | def sel_in(id)
83 | @in = UniMIDI::Input.all[@in_id].close
84 | @in = UniMIDI::Input.use(id)
85 | @in_id = id
86 | @midi_in.close unless @midi_in.nil?
87 | set_listener
88 | end
89 |
90 | #Sends a note to an instrument
91 | def note_send(channel,note,velocity)
92 | @out.puts(0x90+channel-1,note,velocity)
93 | end
94 |
95 | #Release a note. Does not require a duration. Is called when a release signal is received.
96 | def note_rlse(channel,note)
97 | @out.puts(0x80+channel-1,note,0x00)
98 | end
99 |
100 | end
101 |
102 | class NoteSender
103 | attr_reader :note, :chan, :vel
104 | def initialize(note,chan,vel,mute)
105 | @note = note
106 | @chan = chan
107 | @vel = vel
108 | @mute = mute
109 | end
110 |
111 | def play
112 | Pm.note_send(@chan,@note,@vel) unless @mute
113 | end
114 | def stop
115 | Pm.note_rlse(@chan,@note) unless @mute
116 | end
117 |
118 | end
119 |
120 | Pm = Proc_Midi.new(0,0)
--------------------------------------------------------------------------------
/lib/midinous/constants.rb:
--------------------------------------------------------------------------------
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 .
17 |
18 | CANVAS_SIZE = 4400
19 | PI = 3.14159265358979
20 | RED = [0.8,0.0,0.0]
21 | GREEN = [0.0,0.6,0.0]
22 | LGRN = [0.4,0.6,0.4]
23 | BLUE = [0.0,0.0,0.6]
24 | GREY = [0.6,0.6,0.6]
25 | WHITE = [1.0,1.0,1.0]
26 | ORNGE = [1.0,0.5,0.5]
27 | CYAN = [0.0,0.5,1.0]
28 | VLET = [0.6,0.2,0.8]
29 | BLACK = [0.1,0.1,0.1]
30 | BLUGR = [0.2,0.4,0.7]
31 | DGREY = [0.5,0.5,0.5,0.3]
32 |
33 | SCALES = {
34 | "Aeolian" => [2,1,2,2,1,2,2], #Minor
35 | "Altered" => [1,2,1,2,2,2,2],
36 | "Augmented" => [3,1,3,1,3,1],
37 | "Blues" => [3,2,1,1,3,2],
38 | "Chromatic" => [1,1,1,1,1,1,1,1,1,1,1,1],
39 | "Dorian" => [2,1,2,2,2,1,2],
40 | "Flamenco" => [1,3,1,2,1,3,1],
41 | "Half Diminished" => [2,1,2,1,2,2,2],
42 | "Harmonic Major" => [2,2,1,2,1,3,1],
43 | "Harmonic Minor" => [2,1,2,2,1,3,1],
44 | "Hirajoshi" => [4,2,1,4,1],
45 | "Hungarian" => [2,1,3,1,1,3,1],
46 | "Insen" => [1,4,2,4,2],
47 | "Ionian" => [2,2,1,2,2,2,1], #Major
48 | "Iwato" => [1,4,1,4,2],
49 | "Locrian" => [1,2,2,1,2,2,2],
50 | "Locrian Major" => [2,2,1,1,2,2,2],
51 | "Lydian" => [2,2,2,1,2,2,1],
52 | "Lydian Augmented" => [2,2,2,2,1,2,1],
53 | "Pentatonic Major" => [2,2,3,2,3],
54 | "Pentatonic Minor" => [3,2,2,3,2],
55 | "Melodic Minor" => [2,1,2,2,2,2,1],
56 | "Mixolydian" => [2,2,1,2,2,1,2],
57 | "Octatonic Whole" => [2,1,2,1,2,1,2,1],
58 | "Octatonic Half" => [1,2,1,2,1,2,1,2],
59 | "Persian" => [1,3,1,1,2,3,1],
60 | "Phrygian" => [1,2,2,2,1,2,2],
61 | "Prometheus" => [2,2,2,3,1,2],
62 | "Tritone" => [1,3,2,1,3,2],
63 | "Two Semitone Tritone" => [1,1,4,1,1],
64 | "Ukrainian Dorian" => [2,1,3,1,2,1,2],
65 | "Whole Tone" => [2,2,2,2,2,2]
66 | }
67 |
68 | =begin
69 |
70 | Pentatonic
71 | "Hirajoshi"
72 | "Insen"
73 | "Iwato"
74 | "Pentatonic Major"
75 | "Pentatonic Minor"
76 | "Two Semitone Tritone"
77 | Traditional
78 | "Aeolian"
79 | "Dorian"
80 | "Harmonic Major"
81 | "Harmonic Minor"
82 | "Ionian"
83 | "Locrian"
84 | "Lydian"
85 | "Mixolydian"
86 | "Phrygian"
87 | Modified Traditional
88 | "Altered"
89 | "Half Diminished"
90 | "Locrian Major"
91 | "Lydian Augmented"
92 | "Melodic Minor"
93 | "Ukrainian Dorian"
94 | Exotic
95 | "Augmented"
96 | "Blues"
97 | "Flamenco"
98 | "Hungarian"
99 | "Persian"
100 | "Prometheus"
101 | Mathematical
102 | "Chromatic"
103 | "Octatonic Whole"
104 | "Octatonic Half"
105 | "Tritone"
106 | "Whole Tone"
107 |
108 | =end
--------------------------------------------------------------------------------
/lib/midinous/logic.rb:
--------------------------------------------------------------------------------
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 .
17 |
18 | module Logic_Controls #various reusable functions useful for checks and math
19 |
20 | def set_point_speed(tempo,beats,beat_note) #Sets time between each grid point
21 | point_speed = (tempo/60)*CC.grid_spacing #Grid points that will be hit per second
22 | return point_speed #(will be a fraction most of the time)
23 | end
24 |
25 | def round_to_grid(coord) #rounds a coordinate to the nearest snappable grid point
26 | coord.map! do |n|
27 | temp = n % CC.grid_spacing
28 | n -= temp if temp < (CC.grid_spacing/2)
29 | n = n-temp+CC.grid_spacing if temp >= (CC.grid_spacing/2)
30 | n
31 | end
32 | return coord
33 | end
34 |
35 | def sync_diff(stored_time)
36 | new_time = Time.now.to_f*1000
37 | return (CC.ms_per_tick - (new_time - (stored_time + CC.ms_per_tick)))
38 | end
39 |
40 | def relative_pos(xd,yd)
41 | x_sign = nil
42 | y_sign = nil
43 | case
44 | when xd > 0
45 | x_sign = "+"
46 | when xd == 0
47 | x_sign = "0"
48 | when xd < 0
49 | x_sign = "-"
50 | end
51 | case
52 | when yd > 0
53 | y_sign = "+"
54 | when yd == 0
55 | y_sign = "0"
56 | when yd < 0
57 | y_sign = "-"
58 | end
59 |
60 | sign = [x_sign,y_sign]
61 | case sign
62 | when ["+","+"]
63 | return "se"
64 | when ["+","-"]
65 | return "ne"
66 | when ["-","-"]
67 | return "nw"
68 | when ["-","+"]
69 | return "sw"
70 | when ["+","0"]
71 | return "e"
72 | when ["-","0"]
73 | return "w"
74 | when ["0","+"]
75 | return "s"
76 | when ["0","-"]
77 | return "n"
78 | end
79 | end
80 |
81 | def draw_chevron(cr,offset,dir,p)
82 | x = p.x
83 | y = p.y
84 | case dir
85 | when "n"
86 | cr.move_to(x, y+12+offset)
87 | cr.line_to(x+5,y+17+offset)
88 | cr.line_to(x+5,y+21+offset)
89 | cr.line_to(x, y+16+offset)
90 | cr.line_to(x-5,y+21+offset)
91 | cr.line_to(x-5,y+17+offset)
92 | cr.line_to(x, y+12+offset)
93 | when "s"
94 | cr.move_to(x, y-12-offset)
95 | cr.line_to(x+5,y-17-offset)
96 | cr.line_to(x+5,y-21-offset)
97 | cr.line_to(x, y-16-offset)
98 | cr.line_to(x-5,y-21-offset)
99 | cr.line_to(x-5,y-17-offset)
100 | cr.line_to(x, y-12-offset)
101 | when "e"
102 | cr.move_to(x-12-offset,y)
103 | cr.line_to(x-17-offset,y+5)
104 | cr.line_to(x-21-offset,y+5)
105 | cr.line_to(x-16-offset,y)
106 | cr.line_to(x-21-offset,y-5)
107 | cr.line_to(x-17-offset,y-5)
108 | cr.line_to(x-12-offset,y)
109 | when "w"
110 | cr.move_to(x+12+offset,y)
111 | cr.line_to(x+17+offset,y+5)
112 | cr.line_to(x+21+offset,y+5)
113 | cr.line_to(x+16+offset,y)
114 | cr.line_to(x+21+offset,y-5)
115 | cr.line_to(x+17+offset,y-5)
116 | cr.line_to(x+12+offset,y)
117 | end
118 | end
119 |
120 | def round_num_to_grid(num) #2050
121 | temp = num % CC.grid_spacing
122 | num -= temp if temp < (CC.grid_spacing/2)
123 | num = (num-temp)+CC.grid_spacing if temp >= (CC.grid_spacing/2)
124 | return num
125 | end
126 |
127 | def color_to_hex(color)
128 | c_str = "#"
129 | color.each do |n|
130 | n = "%x" % (n*127)
131 | if n.length < 2
132 | c_str = "#{c_str}0#{n}"
133 | else c_str = "#{c_str}#{n}"
134 | end
135 | end
136 | return c_str
137 | end
138 |
139 | def hex_to_color(c_hex)
140 | color = []
141 | color[0] = ((c_hex[1..2].hex).to_f/127)
142 | color[1] = ((c_hex[3..4].hex).to_f/127)
143 | color[2] = ((c_hex[5..6].hex).to_f/127)
144 | return color
145 | end
146 |
147 | def check_bounds(coord,bounds) # returns true if coordinate is colliding with a point bounding box.
148 | if coord[0].between?(bounds[0],bounds[2]) == true &&
149 | coord[1].between?(bounds[1],bounds[3]) == true
150 | return true
151 | else return false
152 | end
153 | end
154 |
155 | def pos_box(bounds) #turn a coordinate-bounded box with unmatching coordinates into one with positive coordinates
156 | if bounds[0] > bounds[2] #Flip the array positions if the box is drawn backwards in any direction.
157 | bounds[0], bounds[2] = bounds[2], bounds[0]
158 | end
159 | if bounds[1] > bounds[3]
160 | bounds[1], bounds[3] = bounds[3], bounds[1]
161 | end
162 | return bounds
163 | end
164 |
165 | end
--------------------------------------------------------------------------------
/lib/midinous/init.rb:
--------------------------------------------------------------------------------
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 .
17 |
18 | require "midinous/extensions"
19 | require "midinous/proc_midi"
20 | require "midinous/constants"
21 | require "midinous/logic"
22 | require "midinous/style/ui"
23 | require "midinous/canvas"
24 | require "midinous/key_bindings"
25 |
26 |
27 | class Init_Prog
28 |
29 | def initialize #Build the user interface, initiate the objects in the program
30 | UI::canvas.set_size_request(CANVAS_SIZE,CANVAS_SIZE)
31 | UI::canvas_h_adj.set_upper(CANVAS_SIZE)
32 | UI::canvas_v_adj.set_upper(CANVAS_SIZE)
33 | grid_center
34 | initialize_provider
35 | apply_style(UI::midinous,@provider)
36 | apply_style(UI::prop_list_view,@provider)
37 | apply_style(UI::file_chooser,@provider)
38 | apply_style(UI::confirmer,@provider)
39 | apply_style(UI::about_window,@provider)
40 | apply_style(UI::notes_window,@provider)
41 | apply_style(UI::scales_window,@provider)
42 | apply_style(UI::hotkeys_window,@provider)
43 | end
44 |
45 | def grid_center
46 | UI::canvas_h_adj.set_value(CANVAS_SIZE/3.1)
47 | UI::canvas_v_adj.set_value(CANVAS_SIZE/2.4)
48 | end
49 |
50 | def apply_style(widget, provider)
51 | style_context = widget.style_context
52 | style_context.add_provider(provider, Gtk::StyleProvider::PRIORITY_USER)
53 | return unless widget.respond_to?(:children)
54 | widget.children.each do |child|
55 | apply_style(child, provider)
56 | end
57 | end
58 |
59 | def initialize_provider
60 | css_file = "#{File.dirname(__FILE__)}/style/midinous_themes.style"
61 | @provider = Gtk::CssProvider.new
62 | @provider.load_from_path(css_file)
63 | end
64 |
65 | end
66 |
67 | init = Init_Prog.new
68 | Times = Time.new
69 |
70 | module Event_Router
71 | extend Key_Bindings
72 |
73 | #For window keep-alives
74 | UI::midinous.signal_connect("delete-event") do
75 | UI.confirm("quit")
76 | true
77 | end
78 | UI::file_chooser.signal_connect("delete-event") do
79 | UI::file_chooser.visible = false
80 | true
81 | end
82 | UI::confirmer.signal_connect("delete-event") do
83 | UI::confirmer.visible = false
84 | true
85 | end
86 | UI::about_window.signal_connect("delete-event") do
87 | UI::about_window.visible = false
88 | true
89 | end
90 | UI::notes_window.signal_connect("delete-event") do
91 | UI::notes_window.visible = false
92 | true
93 | end
94 | UI::scales_window.signal_connect("delete-event") do
95 | UI::scales_window.visible = false
96 | true
97 | end
98 | UI::hotkeys_window.signal_connect("delete-event") do
99 | UI::hotkeys_window.visible = false
100 | true
101 | end
102 |
103 | #For key bindings
104 | UI::midinous.signal_connect("key-press-event") { |obj, event| route_key(event) }
105 |
106 | #For general events
107 | UI::tempo.signal_connect("value-changed") { |obj| CC.set_tempo(obj.value) }
108 | UI::root_select.signal_connect("value-changed") { |obj| CC.set_scale(UI::scale_combo.active_iter[0],obj.value) }
109 |
110 | #For keys
111 | UI::main_tool_1.signal_connect("keybinding-event") {Active_Tool.set_tool(1)}
112 | UI::main_tool_2.signal_connect("keybinding-event") {Active_Tool.set_tool(2)}
113 | UI::main_tool_3.signal_connect("keybinding-event") {Active_Tool.set_tool(3)}
114 | UI::main_tool_4.signal_connect("keybinding-event") {Active_Tool.set_tool(4)}
115 | UI::path_builder.signal_connect("keybinding-event") {CC.canvas_generic("path")}
116 | UI::prop_mod.signal_connect("changed") {Pl.check_input(UI::prop_mod.text)}
117 | UI::prop_mod.signal_connect("keybinding-event") {CC.canvas_generic("prop")}
118 | UI::stop.signal_connect("keybinding-event") {CC.canvas_stop}
119 | UI::play.signal_connect("keybinding-event") {CC.canvas_play}
120 |
121 |
122 | #For clicks
123 | UI::main_tool_1.signal_connect("button-press-event") {Active_Tool.set_tool(1)}
124 | UI::main_tool_2.signal_connect("button-press-event") {Active_Tool.set_tool(2)}
125 | UI::main_tool_3.signal_connect("button-press-event") {Active_Tool.set_tool(3)}
126 | UI::main_tool_4.signal_connect("button-press-event") {Active_Tool.set_tool(4)}
127 | UI::path_builder.signal_connect("button-press-event") {CC.canvas_generic("path")}
128 | UI::prop_list_selection.signal_connect("changed") {Pl.prop_list_select(UI::prop_list_selection.selected)}
129 | UI::prop_mod_button.signal_connect("button-press-event") {CC.canvas_generic("prop")}
130 | UI::stop.signal_connect("button-press-event") {CC.canvas_stop}
131 | UI::play.signal_connect("button-press-event") {CC.canvas_play}
132 | UI::scale_combo.signal_connect("changed") {CC.set_scale(UI::scale_combo.active_iter[0],CC.root_note)}
133 |
134 | #For menu items
135 | UI::in_device_items.each_with_index {|i, idx| i.signal_connect("button-press-event") {UI.set_device(idx,"i")}}
136 | UI::out_device_items.each_with_index {|o, idx| o.signal_connect("button-press-event") {UI.set_device(idx,"o")}}
137 | UI::in_channel_items.each do |i|
138 | i.signal_connect("button-press-event") do
139 | Pm.in_chan = i.label.to_i
140 | UI.regen_status
141 | end
142 | end
143 |
144 | UI::edit_io.signal_connect("button-press-event") do
145 | UI::clear_io_menu_items
146 | Pm.regenerate
147 | UI::set_io_menu_items
148 | UI::in_device_items.each_with_index {|i, idx| i.signal_connect("button-press-event") {UI.set_device(idx,"i")}}
149 | UI::out_device_items.each_with_index {|o, idx| o.signal_connect("button-press-event") {UI.set_device(idx,"o")}}
150 | UI::in_channel_items.each do |i|
151 | i.signal_connect("button-press-event") do
152 | Pm.in_chan = i.label.to_i
153 | UI.regen_status
154 | end
155 | end
156 | end
157 |
158 | UI::help_about.signal_connect("button-press-event") {UI::about_window.visible = true}
159 | UI::help_notes.signal_connect("button-press-event") {UI::notes_window.visible = true}
160 | UI::help_scales.signal_connect("button-press-event") {UI::scales_window.visible = true}
161 | UI::help_hotkeys.signal_connect("button-press-event") {UI::hotkeys_window.visible = true}
162 |
163 | #For file operations
164 | UI::file_new.signal_connect("button-press-event") {UI.confirm("new")}
165 | UI::file_open.signal_connect("button-press-event") {UI.file_oper("open")}
166 | UI::file_save.signal_connect("button-press-event") {UI.file_oper("save")}
167 | UI::file_save_as.signal_connect("button-press-event") {UI.file_oper("saveas")}
168 | UI::file_quit.signal_connect("button-press-event") {UI.confirm("quit")}
169 |
170 | UI::confirmer_confirm.signal_connect("button-press-event"){UI.confirm_act("yes")}
171 | UI::confirmer_cancel.signal_connect("button-press-event") {UI.confirm_act("no")}
172 |
173 | UI::file_operation.signal_connect("button-press-event") {UI.file_oper_act("yes")}
174 | UI::file_cancel.signal_connect("button-press-event") {UI.file_oper_act("no")}
175 | UI::file_chooser.signal_connect("selection-changed") {UI.file_input_check("chooser")}
176 | UI::file_name.signal_connect("changed") {UI.file_input_check("name")}
177 |
178 | #For accelerators
179 | UI::menu_commands.connect(Gdk::Keyval::KEY_n,4,0) {UI.confirm("new")}
180 | UI::menu_commands.connect(Gdk::Keyval::KEY_o,4,0) {UI.file_oper("open")}
181 | UI::menu_commands.connect(Gdk::Keyval::KEY_s,4,0) {UI.file_oper("save")}
182 | UI::menu_commands.connect(Gdk::Keyval::KEY_s,5,0) {UI.file_oper("saveas")}
183 | UI::menu_commands.connect(Gdk::Keyval::KEY_q,4,0) {UI.confirm("quit")}
184 | UI::canvas_commands.connect(Gdk::Keyval::KEY_a,4,0) {Pl.select_all if Active_Tool.tool_id == 1}
185 | UI::canvas_commands.connect(Gdk::Keyval::KEY_x,4,0) {Pl.copy_points("cut") if Active_Tool.tool_id == 1}
186 | UI::canvas_commands.connect(Gdk::Keyval::KEY_c,4,0) {Pl.copy_points("copy") if Active_Tool.tool_id == 1}
187 | UI::canvas_commands.connect(Gdk::Keyval::KEY_v,4,0) {Pl.paste_points if Active_Tool.tool_id == 1}
188 |
189 | #Canvas Events
190 | UI::canvas.signal_connect("button-press-event") { |obj, event| CC.canvas_press(obj,event) }
191 | UI::canvas.signal_connect("motion-notify-event") { |obj, event| CC.canvas_drag(obj,event) }
192 | UI::canvas.signal_connect("button-release-event") { |obj, event| CC.canvas_release(obj,event) }
193 | UI::canvas.signal_connect("draw") { |obj, cr| CC.canvas_draw(cr) }
194 | UI::canvas.signal_connect("delete-selected") {Pl.delete_points}
195 | UI::canvas.signal_connect("mute-toggle") {Pl.mute_points}
196 | UI::canvas.signal_connect("beat-up") {CC.canvas_grid_change("+")}
197 | UI::canvas.signal_connect("beat-dn") {CC.canvas_grid_change("-")}
198 | UI::canvas.signal_connect("beat-note-up") {CC.canvas_grid_change("++")}
199 | UI::canvas.signal_connect("beat-note-dn") {CC.canvas_grid_change("--")}
200 | UI::canvas.signal_connect("travel-event") {CC.canvas_travel}
201 | UI::canvas.signal_connect("cycle-play-mode-bck") {Pl.play_mode_rotate(-1)}
202 | UI::canvas.signal_connect("cycle-play-mode-fwd") {Pl.play_mode_rotate(1)}
203 | UI::canvas.signal_connect("set-start") {Pl.set_start}
204 | UI::canvas.signal_connect("del-path-to") {Pl.delete_paths_to}
205 | UI::canvas.signal_connect("del-path-from") {Pl.delete_paths_from}
206 | UI::canvas.signal_connect("set-path-mode-h") {Pl.set_path_mode("horz")}
207 | UI::canvas.signal_connect("set-path-mode-v") {Pl.set_path_mode("vert")}
208 | UI::canvas.signal_connect("note-inc-up") {Pl.inc_note(1)}
209 | UI::canvas.signal_connect("note-inc-dn") {Pl.inc_note(-1)}
210 | UI::canvas.signal_connect("path-rotate-bck") {Pl.path_rotate("-")}
211 | UI::canvas.signal_connect("path-rotate-fwd") {Pl.path_rotate("+")}
212 | end
213 |
214 |
--------------------------------------------------------------------------------
/lib/midinous/canvas.rb:
--------------------------------------------------------------------------------
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 .
17 |
18 | require "midinous/points"
19 |
20 | class Canvas_Control
21 | include Logic_Controls
22 | attr_accessor :nouspoints, :travelers, :beats, :beat_note, :scale, :root_note,
23 | :tempo, :repeaters, :queued_note_plays, :queued_note_stops
24 | attr_reader :grid_spacing, :midi_sync, :ms_per_tick, :dragging, :start_time,
25 | :mouse_last_pos, :scale_posns
26 | def initialize
27 | @mouse_last_pos = nil
28 | @sel_box = nil
29 | @selecting = false
30 | @sel_white = [0.8,0.8,0.8,0.1] #selection box colors
31 | @sel_blue = [0,0.5,1,0.5] #selection box colors
32 | @point_origin = nil
33 | @path_origin = nil
34 | @point_move = nil
35 | @dragging = false
36 | @diff = [0,0]
37 | @nouspoints = []
38 | @travelers = []
39 | @starters = []
40 | @repeaters = []
41 | @scale = "Chromatic"
42 | @root_note = 60
43 | @scale_notes = []
44 | @scale_posns = []
45 | set_scale(@scale,@root_note)
46 | @tempo = 120.000
47 | @midi_sync = 0.000
48 | @path_sourced = false
49 | @attempt_path = false
50 | @ms_per_tick = 125.000 #default tempo of 120bpm
51 | @beats = 4 #number of beats in a whole note -- should be reasonably between 1 and 16
52 | @beat_note = 4 #as a fraction of a whole note -- should be between 2 and 16 via powers of 2
53 | @grid_spacing = 35
54 | @queued_note_plays = []
55 | @queued_note_stops = []
56 | end
57 |
58 | def set_tempo(tempo)
59 | @tempo = tempo
60 | @ms_per_tick = (1000 * (15 / @tempo)) / (@beat_note / 4)
61 | end
62 | def set_scale(scale_text,root)
63 | @scale = scale_text
64 | @root_note = root
65 | scale = SCALES[scale_text]
66 | slen = scale.length
67 | @scale_notes = []
68 | @scale_notes << root
69 |
70 | c = 0
71 | note = root
72 | while note < 127
73 | note += scale[c]
74 | @scale_notes << note unless note > 127
75 | c = (c + 1) % slen
76 | end
77 |
78 | c = 0
79 | note = root
80 | while note > 0
81 | note -= scale.reverse[c]
82 | @scale_notes << note unless note < 0
83 | c = (c + 1) % slen
84 | end
85 | @scale_notes.sort!
86 |
87 | (0..127).each do |num|
88 | @scale_posns[num] = false
89 | @scale_posns[num] = true if @scale_notes.find {|f| f == num} != nil
90 | end
91 | end
92 |
93 | def canvas_generic(string) #Used as a pseudo-handler between classes
94 | case string
95 | when "path"
96 | if !@nouspoints.empty? && @nouspoints.find_all(&:pathable).any?
97 | @nouspoints = Pl.add_path(@nouspoints)
98 | @nouspoints, @path_sourced = Pl.cancel_path(@nouspoints)
99 | UI::canvas.queue_draw
100 | end
101 | when "prop"
102 | @nouspoints = Pl.modify_properties(@nouspoints)
103 | Pl.populate_prop(@nouspoints)
104 | UI::canvas.queue_draw
105 | end
106 | end
107 |
108 | def canvas_play
109 |
110 | if @nouspoints.find(&:traveler_start)
111 | @playing = true
112 | @nouspoints.find_all {|n| n.path_to.length > 1}.each {|p| p.path_to.each {|e| p.path_to_memory << e}}
113 | UI::play.sensitive = false
114 | UI::stop.sensitive = true
115 | end
116 |
117 | @nouspoints.find_all(&:traveler_start).each do |n|
118 | @starters << Starter.new(nil,n,nil)
119 | UI::canvas.queue_draw
120 | @queued_note_plays.each {|o| o.play}
121 | signal_chain(n,n.note)
122 | end
123 | @stored_time = Time.now.to_f*1000
124 | canvas_timeout(@ms_per_tick) #Start sequence
125 | end
126 |
127 | def canvas_timeout(ms)
128 | GLib::Timeout.add(ms) do
129 | UI::canvas.signal_emit('travel-event') unless !@playing
130 | false
131 | end
132 | end
133 |
134 | def canvas_travel
135 | @queued_note_plays = []
136 | canvas_stop if !@playing ||
137 | (@travelers.length == 0 &&
138 | @starters.length == 0 &&
139 | @repeaters.length == 0)
140 |
141 | @starters.each {|s| s.travel}
142 | @travelers.each {|t| t.travel}
143 | @travelers.find_all(&:reached).each do |t|
144 | signal_chain(t.dest,t.played_note) #Pass the last played note here. Gather the played note from the first traveler creation
145 | t.reached = false
146 | end
147 | @repeaters.each {|r| r.repeat}
148 |
149 | @queued_note_stops.each {|n| n.stop}
150 | @queued_note_plays.each {|n| n.play}
151 | @starters.reject!(&:remove)
152 | @travelers.reject!(&:remove)
153 | @repeaters.reject!(&:remove)
154 | @queued_note_stops = []
155 |
156 | canvas_timeout(sync_diff(@stored_time))
157 | @stored_time += @ms_per_tick
158 | UI::canvas.queue_draw
159 | end
160 |
161 | def canvas_stop
162 | @playing = false
163 | @starters = []
164 | @repeaters = []
165 | @queued_note_plays = []
166 | @queued_note_stops = []
167 | @nouspoints.find_all {|n| n.path_to.length > 1}.each {|p| p.reset_path_to}
168 | UI::canvas.queue_draw
169 | @nouspoints.each do |n|
170 | n.playing = false
171 | n.repeating = false
172 | Pm.note_rlse(n.channel,n.note) unless n.note.to_s.include?("+") || n.note.to_s.include?("-")
173 | end
174 | @travelers.each do |t|
175 | Pm.note_rlse(t.dest.channel,t.played_note) unless t.played_note == nil
176 | end
177 | @travelers = []
178 | UI::stop.sensitive = false
179 | UI::play.sensitive = true
180 | end
181 |
182 | def signal_chain(point,pn) #pn = played note
183 | case point.play_modes[0]
184 | when "robin"
185 | p = point.path_to.first
186 | if p
187 | @travelers << Traveler.new(point,p,pn)
188 | point.path_to.rotate!
189 | end
190 | when "split"
191 | point.path_to.each {|p| @travelers << Traveler.new(point,p,pn)}
192 | when "portal"
193 | point.path_to.each do |p|
194 | @starters << Starter.new(point,p,pn)
195 | UI::canvas.queue_draw
196 | #@queued_note_plays.each {|o| o.play}
197 | signal_chain(p,pn)
198 | end
199 | when "random"
200 | p = point.path_to.sample
201 | if p
202 | @travelers << Traveler.new(point,p,pn)
203 | point.path_to.rotate!(rand(point.path_to.length))
204 | end
205 | end
206 | end
207 |
208 | def canvas_grid_change(dir)
209 | prev_beat_note = @beat_note
210 | case dir
211 | when "+"
212 | @beats += 1
213 | @beats = 16 if @beats > 16
214 | when "-"
215 | @beats -= 1
216 | @beats = 1 if @beats < 1
217 | when "++"
218 | @beat_note *= 2
219 | @beat_note = 16 if @beat_note > 16
220 | when "--"
221 | @beat_note /= 2
222 | @beat_note = 2 if @beat_note < 2
223 | end
224 | if @beat_note != prev_beat_note
225 | case dir
226 | when "++"
227 | @ms_per_tick /= 2
228 | when "--"
229 | @ms_per_tick *= 2
230 | end
231 | end
232 | UI::t_sig.text = "#{@beats}/#{@beat_note}"
233 | UI::canvas.queue_draw
234 | end
235 |
236 | def canvas_press(obj,event)
237 | UI::logic_controls.focus = true
238 | case Active_Tool.tool_id
239 | when 1
240 | @sel_box = [event.x,event.y,event.x,event.y]
241 | @selecting = true
242 | when 2
243 | @point_origin = [event.x,event.y]
244 | when 3
245 | @point_move = [event.x,event.y,event.x,event.y]
246 | when 4
247 | @path_origin = [event.x,event.y]
248 | end
249 | obj.queue_draw
250 | end
251 | def canvas_drag(obj,event)
252 | @dragging = false
253 | @mouse_last_pos = [event.x,event.y]
254 | case
255 | when (@selecting && @sel_box)
256 | @dragging = true
257 | @sel_box[2] = event.x
258 | @sel_box[3] = event.y
259 | obj.queue_draw
260 | when (@point_origin)
261 | @dragging = true
262 | @point_origin[0] = event.x
263 | @point_origin[1] = event.y
264 | when (@point_move)
265 | @dragging = true
266 | # difference in movement of the point, cumulative until mouse released
267 | @diff = round_to_grid([(event.x - @point_move[0]) , (event.y - @point_move[1])])
268 | @point_move[2] = event.x
269 | @point_move[3] = event.y
270 | obj.queue_draw
271 | end
272 | end
273 | def canvas_release(obj,event)
274 | @dragging = false
275 | case Active_Tool.tool_id
276 | when 1
277 | unless !@sel_box
278 | @sel_box = pos_box(@sel_box)
279 | @nouspoints = Pl.select_points(@sel_box,@nouspoints)
280 | @sel_box = nil
281 | @selecting = false
282 | end
283 | when 2 #Add a point where/when the tool is released
284 | unless !@point_origin
285 | @nouspoints = Pl.add_point(round_to_grid(@point_origin),@nouspoints)
286 | @point_origin = nil
287 | end
288 | when 3 #move point(s) designated by the move stencil
289 | @nouspoints = Pl.move_points(@diff,@nouspoints)
290 | @point_move = nil
291 | @diff = [0,0]
292 | when 4 #select singular point
293 | @nouspoints, @path_sourced = Pl.select_path_point(@path_origin,@nouspoints,@path_sourced)
294 | end
295 | obj.queue_draw
296 | end
297 |
298 | def canvas_bg_draw(cr)
299 | # fill background with black
300 | cr.set_source_rgb(BLACK)
301 | cr.paint
302 | cr.set_source_rgb(BLUGR)
303 | x = @grid_spacing
304 | while x < CANVAS_SIZE
305 | y = @grid_spacing
306 | while y < CANVAS_SIZE
307 | cr.circle(x,y,1)
308 | cr.fill
309 | y += @grid_spacing
310 | end
311 | x += @grid_spacing
312 | end
313 | #Handle measure drawing via notches on paths instead of this, maybe
314 | cr.set_source_rgba(DGREY)
315 | x = @grid_spacing
316 | y = CANVAS_SIZE - @grid_spacing
317 | while x < CANVAS_SIZE
318 | cr.move_to(x,@grid_spacing)
319 | cr.line_to(x,CANVAS_SIZE-@grid_spacing)
320 | cr.set_line_width(1)
321 | cr.stroke
322 | x += @grid_spacing*@beats
323 | end
324 | y = @grid_spacing
325 | x = CANVAS_SIZE - @grid_spacing
326 | while y < CANVAS_SIZE
327 | cr.move_to(@grid_spacing,y)
328 | cr.line_to(CANVAS_SIZE-@grid_spacing,y)
329 | cr.set_line_width(1)
330 | cr.stroke
331 | y += @grid_spacing*@beats
332 | end
333 | end
334 |
335 | def canvas_draw(cr)
336 | canvas_bg_draw(cr)
337 | case #These draw events are for in-progress/temporary activities
338 | when(@sel_box)
339 | width = @sel_box[2] - @sel_box[0]
340 | height = @sel_box[3] - @sel_box[1]
341 |
342 | cr.set_source_rgba(@sel_blue)
343 | cr.rectangle(@sel_box[0],@sel_box[1],width,height)
344 | cr.set_line_width(2)
345 | cr.stroke
346 | when(@point_move)
347 | start_coord = [@point_move[0],@point_move[1]]
348 | end_coord = [@point_move[2],@point_move[3]]
349 | start_coord = round_to_grid(start_coord)
350 | end_coord = round_to_grid(end_coord)
351 |
352 | cr.move_to(start_coord[0],start_coord[1])
353 | cr.set_source_rgba(RED)
354 | cr.line_to(end_coord[0],end_coord[1])
355 | cr.set_line_width(2)
356 | cr.stroke
357 | cr.rounded_rectangle(end_coord[0]-10,end_coord[1]-10,20,20,2,2)
358 | cr.set_line_width(2)
359 | cr.stroke
360 | end
361 |
362 | #Set the scene if the current tool does not permit a style
363 | case Active_Tool.tool_id
364 | when 1
365 | @nouspoints, @path_sourced = Pl.cancel_path(@nouspoints)
366 | when 2
367 | @nouspoints, @path_sourced = Pl.cancel_path(@nouspoints)
368 | when 3
369 | @nouspoints, @path_sourced = Pl.cancel_path(@nouspoints)
370 | when 4
371 | @nouspoints = Pl.cancel_selected(@nouspoints)
372 | end
373 |
374 | #Draw all the points and paths last
375 | @nouspoints.each { |n| n.set_path_color }
376 | @nouspoints.each { |n| n.path_draw(cr) }
377 | @nouspoints.each { |n| n.caret_draw(cr) }
378 | @nouspoints.each { |n| n.draw(cr) }
379 |
380 | end
381 | end
382 |
383 | CC = Canvas_Control.new
--------------------------------------------------------------------------------
/lib/midinous/style/ui.rb:
--------------------------------------------------------------------------------
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 .
17 |
18 | class GtkRadioButtonEx < Gtk::RadioButton
19 | type_register
20 | def initialize
21 | super()
22 | end
23 | define_signal('keybinding-event',nil,nil,nil)
24 | end
25 | class GtkButtonEx < Gtk::Button
26 | type_register
27 | def initialize
28 | super()
29 | end
30 | define_signal('keybinding-event',nil,nil,nil)
31 | end
32 | class GtkCanvas < Gtk::DrawingArea
33 | type_register
34 | def initialize
35 | super()
36 | end
37 | define_signal('delete-selected',nil,nil,nil)
38 | define_signal('mute-toggle',nil,nil,nil)
39 | define_signal('beat-up',nil,nil,nil)
40 | define_signal('beat-dn',nil,nil,nil)
41 | define_signal('beat-note-up',nil,nil,nil)
42 | define_signal('beat-note-dn',nil,nil,nil)
43 | define_signal('travel-event',nil,nil,nil)
44 | define_signal('cycle-play-mode-bck',nil,nil,nil)
45 | define_signal('cycle-play-mode-fwd',nil,nil,nil)
46 | define_signal('set-start',nil,nil,nil)
47 | define_signal('del-path-to',nil,nil,nil)
48 | define_signal('del-path-from',nil,nil,nil)
49 | define_signal('set-path-mode-h',nil,nil,nil)
50 | define_signal('set-path-mode-v',nil,nil,nil)
51 | define_signal('note-inc-up',nil,nil,nil)
52 | define_signal('note-inc-dn',nil,nil,nil)
53 | define_signal('path-rotate-bck',nil,nil,nil)
54 | define_signal('path-rotate-fwd',nil,nil,nil)
55 | end
56 | class GtkPropEntry < Gtk::Entry
57 | type_register
58 | def initialize
59 | super()
60 | end
61 | define_signal('keybinding-event',nil,nil,nil)
62 | end
63 |
64 | class UI_Elements
65 | include Logic_Controls
66 | # Construct a Gtk::Builder instance and load our UI description
67 | attr_reader :menu_commands,:canvas_commands,:in_device_items,
68 | :out_device_items,:in_channel_items
69 | def initialize
70 | @current_file = nil
71 | @operation_file = nil
72 | @current_window = nil
73 | @scale_iters = []
74 | @in_channel_items = []
75 | @in_device_items = []
76 | @out_device_items = []
77 | end
78 | def build_ui
79 | builder_file = "#{File.dirname(__FILE__)}/midinous.glade"
80 |
81 | # Connect signal handlers to the constructed widgets
82 | @builder = Gtk::Builder.new(:file => builder_file)
83 |
84 | #Windows
85 | def midinous
86 | @builder.get_object("midinous")
87 | end
88 | def file_chooser
89 | @builder.get_object("file_chooser")
90 | end
91 | def confirmer
92 | @builder.get_object("confirmer")
93 | end
94 | def about_window
95 | @builder.get_object("about_window")
96 | end
97 | def notes_window
98 | @builder.get_object("notes_window")
99 | end
100 | def scales_window
101 | @builder.get_object("scales_window")
102 | end
103 | def hotkeys_window
104 | @builder.get_object("hotkeys_window")
105 | end
106 |
107 | #Menus
108 | def edit_menu
109 | @builder.get_object("edit_menu")
110 | end
111 | def input_menu
112 | @builder.get_object("input_menu")
113 | end
114 | def output_menu
115 | @builder.get_object("output_menu")
116 | end
117 | def input_channel_menu
118 | @builder.get_object("input_channel_menu")
119 | end
120 | def help_menu
121 | @builder.get_object("help_menu")
122 | end
123 |
124 | #Menu Items
125 | def file_new
126 | @builder.get_object("file_new")
127 | end
128 | def file_open
129 | @builder.get_object("file_open")
130 | end
131 | def file_save
132 | @builder.get_object("file_save")
133 | end
134 | def file_save_as
135 | @builder.get_object("file_save_as")
136 | end
137 | def file_quit
138 | @builder.get_object("file_quit")
139 | end
140 | def edit_io
141 | @builder.get_object("edit_io")
142 | end
143 | def help_about
144 | @builder.get_object("help_about")
145 | end
146 | def help_notes
147 | @builder.get_object("help_notes")
148 | end
149 | def help_scales
150 | @builder.get_object("help_scales")
151 | end
152 | def help_hotkeys
153 | @builder.get_object("help_hotkeys")
154 | end
155 |
156 | #Drawing Areas
157 | def canvas
158 | @builder.get_object("canvas")
159 | end
160 | def canvas_viewport
161 | @builder.get_object("canvas_viewport")
162 | end
163 | def canvas_scroll_window
164 | @builder.get_object("canvas_scroll_window")
165 | end
166 |
167 | #Adjustments
168 | def canvas_h_adj
169 | @builder.get_object("canvas_scroll_h")
170 | end
171 | def canvas_v_adj
172 | @builder.get_object("canvas_scroll_v")
173 | end
174 | def tempo_adj
175 | @builder.get_object("tempo_adj")
176 | end
177 | def root_adj
178 | @builder.get_object("root_adj")
179 | end
180 |
181 | #Buttons
182 | def main_tool_1
183 | @builder.get_object("main_tool_1")
184 | end
185 | def main_tool_2
186 | @builder.get_object("main_tool_2")
187 | end
188 | def main_tool_3
189 | @builder.get_object("main_tool_3")
190 | end
191 | def main_tool_4
192 | @builder.get_object("main_tool_4")
193 | end
194 | def path_builder
195 | @builder.get_object("path_builder")
196 | end
197 | def prop_mod_button
198 | @builder.get_object("prop_mod_button")
199 | end
200 | def play
201 | @builder.get_object("play")
202 | end
203 | def stop
204 | @builder.get_object("stop")
205 | end
206 | def tempo
207 | @builder.get_object("tempo")
208 | end
209 | def root_select
210 | @builder.get_object("root_select")
211 | end
212 | def file_operation
213 | @builder.get_object("file_operation")
214 | end
215 | def file_cancel
216 | @builder.get_object("file_cancel")
217 | end
218 | def confirmer_confirm
219 | @builder.get_object("confirmer_confirm")
220 | end
221 | def confirmer_cancel
222 | @builder.get_object("confirmer_cancel")
223 | end
224 |
225 | #Button Areas
226 | def logic_controls
227 | @builder.get_object("logic_controls")
228 | end
229 |
230 | #Text Areas
231 | def tool_descrip
232 | @builder.get_object("tool_descrip")
233 | end
234 | def status_area
235 | @builder.get_object("status_area")
236 | end
237 | def prop_mod
238 | @builder.get_object("prop_mod")
239 | end
240 | def t_sig
241 | @builder.get_object("t_sig")
242 | end
243 | def file_name
244 | @builder.get_object("file_name")
245 | end
246 |
247 | #Labels
248 | def perf_label
249 | @builder.get_object("perf_label")
250 | end
251 | def tool_label
252 | @builder.get_object("tool_label")
253 | end
254 | def tempo_label
255 | @builder.get_object("tempo_label")
256 | end
257 | def property_label
258 | @builder.get_object("property_label")
259 | end
260 | def modify_label
261 | @builder.get_object("modify_label")
262 | end
263 | def t_sig_label
264 | @builder.get_object("t_sig_label")
265 | end
266 | def confirmer_label
267 | @builder.get_object("confirmer_label")
268 | end
269 | def scale_label
270 | @builder.get_object("scale_label")
271 | end
272 |
273 | #Point Property Tree
274 | def prop_list_model
275 | @builder.get_object("prop_list_model")
276 | end
277 | def prop_list_view
278 | @builder.get_object("prop_list_view")
279 | end
280 | def prop_list
281 | @builder.get_object("prop_list")
282 | end
283 | def prop_list_col1_h
284 | @builder.get_object("prop_list_col1_h")
285 | end
286 | def prop_list_col2_h
287 | @builder.get_object("prop_list_col2_h")
288 | end
289 | def prop_list_col1
290 | @builder.get_object("prop_list_col1")
291 | end
292 | def prop_list_col2
293 | @builder.get_object("prop_list_col2")
294 | end
295 | def prop_list_selection
296 | @builder.get_object("prop_list_selection")
297 | end
298 |
299 | #Scale Selection Combo Box
300 | def scale_combo
301 | @builder.get_object("scale_combo")
302 | end
303 | def scale_tree_model
304 | @builder.get_object("scale_tree_model")
305 | end
306 | def scale_display
307 | @builder.get_object("scale_display")
308 | end
309 | def clear_io_menu_items
310 | @in_channel_items.each {|i| input_channel_menu.remove(i)}
311 | @in_channel_items = []
312 | @in_device_items.each {|i| input_menu.remove(i)}
313 | @in_device_items = []
314 | @out_device_items.each {|o| output_menu.remove(o)}
315 | @out_device_items = []
316 | end
317 | def set_io_menu_items
318 | unless Pm.in_list.length <= 1
319 | 16.times.with_index {|i| @in_channel_items << Gtk::ImageMenuItem.new(label: (i+1).to_s)}
320 | @in_channel_items.each {|i| input_channel_menu.append(i)}
321 |
322 | Pm.in_list.each {|i| @in_device_items << Gtk::ImageMenuItem.new(label: i.name)}
323 | @in_device_items.each {|i| input_menu.append(i)}
324 | end
325 | Pm.out_list.each {|o| @out_device_items << Gtk::ImageMenuItem.new(label: o.name)}
326 | @out_device_items.each {|o| output_menu.append(o)}
327 |
328 | input_channel_menu.show_all
329 | input_menu.show_all
330 | output_menu.show_all
331 | end
332 |
333 | def set_device(id,type)
334 | case type
335 | when 'i'
336 | Pm.sel_in(id)
337 | when 'o'
338 | Pm.sel_out(id)
339 | end
340 | regen_status
341 | end
342 |
343 | def regen_status
344 | unless Pm.in_list.length <= 1
345 | status_area.text = "Using: Output[#{Pm.out_list[Pm.out_id].name}] "\
346 | "Input[#{Pm.in_list[Pm.in_id].name} Channel: #{Pm.in_chan}]"
347 | else
348 | status_area.text = "Using: Output[#{Pm.out_list[Pm.out_id].name}]"
349 | end
350 | end
351 |
352 | #Set up accelerators (keyboard shortcuts)
353 | @menu_commands = Gtk::AccelGroup.new
354 | @canvas_commands = Gtk::AccelGroup.new
355 | midinous.add_accel_group(@menu_commands)
356 | midinous.add_accel_group(@canvas_commands)
357 |
358 | scale_cat_1 = scale_tree_model.append(nil)
359 | scale_cat_1_sub_01 = scale_tree_model.append(scale_cat_1)
360 | scale_cat_1_sub_02 = scale_tree_model.append(scale_cat_1)
361 | scale_cat_1_sub_03 = scale_tree_model.append(scale_cat_1)
362 | scale_cat_1_sub_04 = scale_tree_model.append(scale_cat_1)
363 | scale_cat_1_sub_05 = scale_tree_model.append(scale_cat_1)
364 | scale_cat_1_sub_06 = scale_tree_model.append(scale_cat_1)
365 | scale_cat_2 = scale_tree_model.append(nil)
366 | scale_cat_2_sub_01 = scale_tree_model.append(scale_cat_2)
367 | scale_cat_2_sub_02 = scale_tree_model.append(scale_cat_2)
368 | scale_cat_2_sub_03 = scale_tree_model.append(scale_cat_2)
369 | scale_cat_2_sub_04 = scale_tree_model.append(scale_cat_2)
370 | scale_cat_2_sub_05 = scale_tree_model.append(scale_cat_2)
371 | scale_cat_2_sub_06 = scale_tree_model.append(scale_cat_2)
372 | scale_cat_2_sub_07 = scale_tree_model.append(scale_cat_2)
373 | scale_cat_2_sub_08 = scale_tree_model.append(scale_cat_2)
374 | scale_cat_2_sub_09 = scale_tree_model.append(scale_cat_2)
375 | scale_cat_3 = scale_tree_model.append(nil)
376 | scale_cat_3_sub_01 = scale_tree_model.append(scale_cat_3)
377 | scale_cat_3_sub_02 = scale_tree_model.append(scale_cat_3)
378 | scale_cat_3_sub_03 = scale_tree_model.append(scale_cat_3)
379 | scale_cat_3_sub_04 = scale_tree_model.append(scale_cat_3)
380 | scale_cat_3_sub_05 = scale_tree_model.append(scale_cat_3)
381 | scale_cat_3_sub_06 = scale_tree_model.append(scale_cat_3)
382 | scale_cat_4 = scale_tree_model.append(nil)
383 | scale_cat_4_sub_01 = scale_tree_model.append(scale_cat_4)
384 | scale_cat_4_sub_02 = scale_tree_model.append(scale_cat_4)
385 | scale_cat_4_sub_03 = scale_tree_model.append(scale_cat_4)
386 | scale_cat_4_sub_04 = scale_tree_model.append(scale_cat_4)
387 | scale_cat_4_sub_05 = scale_tree_model.append(scale_cat_4)
388 | scale_cat_4_sub_06 = scale_tree_model.append(scale_cat_4)
389 | scale_cat_5 = scale_tree_model.append(nil)
390 | scale_cat_5_sub_01 = scale_tree_model.append(scale_cat_5)
391 | scale_cat_5_sub_02 = scale_tree_model.append(scale_cat_5)
392 | scale_cat_5_sub_03 = scale_tree_model.append(scale_cat_5)
393 | scale_cat_5_sub_04 = scale_tree_model.append(scale_cat_5)
394 | scale_cat_5_sub_05 = scale_tree_model.append(scale_cat_5)
395 |
396 | scale_cat_1 [0] = "Pentatonic"
397 | scale_cat_1_sub_01[0] = "Hirajoshi"
398 | scale_cat_1_sub_02[0] = "Insen"
399 | scale_cat_1_sub_03[0] = "Iwato"
400 | scale_cat_1_sub_04[0] = "Pentatonic Major"
401 | scale_cat_1_sub_05[0] = "Pentatonic Minor"
402 | scale_cat_1_sub_06[0] = "Two Semitone Tritone"
403 | scale_cat_2 [0] = "Traditional"
404 | scale_cat_2_sub_01[0] = "Aeolian"
405 | scale_cat_2_sub_02[0] = "Dorian"
406 | scale_cat_2_sub_03[0] = "Harmonic Major"
407 | scale_cat_2_sub_04[0] = "Harmonic Minor"
408 | scale_cat_2_sub_05[0] = "Ionian"
409 | scale_cat_2_sub_06[0] = "Locrian"
410 | scale_cat_2_sub_07[0] = "Lydian"
411 | scale_cat_2_sub_08[0] = "Mixolydian"
412 | scale_cat_2_sub_09[0] = "Phrygian"
413 | scale_cat_3 [0] = "Modified Traditional"
414 | scale_cat_3_sub_01[0] = "Altered"
415 | scale_cat_3_sub_02[0] = "Half Diminished"
416 | scale_cat_3_sub_03[0] = "Locrian Major"
417 | scale_cat_3_sub_04[0] = "Lydian Augmented"
418 | scale_cat_3_sub_05[0] = "Melodic Minor"
419 | scale_cat_3_sub_06[0] = "Ukrainian Dorian"
420 | scale_cat_4 [0] = "Exotic"
421 | scale_cat_4_sub_01[0] = "Augmented"
422 | scale_cat_4_sub_02[0] = "Blues"
423 | scale_cat_4_sub_03[0] = "Flamenco"
424 | scale_cat_4_sub_04[0] = "Hungarian"
425 | scale_cat_4_sub_05[0] = "Persian"
426 | scale_cat_4_sub_06[0] = "Prometheus"
427 | scale_cat_5 [0] = "Mathematical"
428 | scale_cat_5_sub_01[0] = "Chromatic"
429 | scale_cat_5_sub_02[0] = "Octatonic Whole"
430 | scale_cat_5_sub_03[0] = "Octatonic Half"
431 | scale_cat_5_sub_04[0] = "Tritone"
432 | scale_cat_5_sub_05[0] = "Whole Tone"
433 |
434 | @scale_iters << scale_cat_1_sub_01
435 | @scale_iters << scale_cat_1_sub_02
436 | @scale_iters << scale_cat_1_sub_03
437 | @scale_iters << scale_cat_1_sub_04
438 | @scale_iters << scale_cat_1_sub_05
439 | @scale_iters << scale_cat_1_sub_06
440 | @scale_iters << scale_cat_2_sub_01
441 | @scale_iters << scale_cat_2_sub_02
442 | @scale_iters << scale_cat_2_sub_03
443 | @scale_iters << scale_cat_2_sub_04
444 | @scale_iters << scale_cat_2_sub_05
445 | @scale_iters << scale_cat_2_sub_06
446 | @scale_iters << scale_cat_2_sub_07
447 | @scale_iters << scale_cat_2_sub_08
448 | @scale_iters << scale_cat_2_sub_09
449 | @scale_iters << scale_cat_3_sub_01
450 | @scale_iters << scale_cat_3_sub_02
451 | @scale_iters << scale_cat_3_sub_03
452 | @scale_iters << scale_cat_3_sub_04
453 | @scale_iters << scale_cat_3_sub_05
454 | @scale_iters << scale_cat_3_sub_06
455 | @scale_iters << scale_cat_4_sub_01
456 | @scale_iters << scale_cat_4_sub_02
457 | @scale_iters << scale_cat_4_sub_03
458 | @scale_iters << scale_cat_4_sub_04
459 | @scale_iters << scale_cat_4_sub_05
460 | @scale_iters << scale_cat_4_sub_06
461 | @scale_iters << scale_cat_5_sub_01
462 | @scale_iters << scale_cat_5_sub_02
463 | @scale_iters << scale_cat_5_sub_03
464 | @scale_iters << scale_cat_5_sub_04
465 | @scale_iters << scale_cat_5_sub_05
466 |
467 | scale_combo.active = 4
468 | scale_combo.active_iter = scale_cat_5_sub_01
469 |
470 | #Initialize the elements of the screen
471 | midinous.add_events("key-press-mask")
472 | canvas.add_events("button-press-mask")
473 | canvas.add_events("button-release-mask")
474 | canvas.add_events("pointer-motion-mask")
475 | prop_mod.add_events("key-press-mask")
476 |
477 | path_builder.sensitive = false
478 | prop_mod_button.sensitive = false
479 | stop.sensitive = false
480 |
481 | tool_descrip.text = "Select"
482 | t_sig.text = "4/4"
483 |
484 | perf_label.markup = "#{perf_label.text}"
485 | tempo_label.markup = "#{tempo_label.text}"
486 | tool_label.markup = "#{tool_label.text}"
487 | property_label.markup = "#{property_label.text}"
488 | modify_label.markup = "#{modify_label.text}"
489 | t_sig_label.markup = "#{t_sig_label.text}"
490 | scale_label.markup = "#{scale_label.text}"
491 |
492 | set_io_menu_items
493 | regen_status
494 |
495 |
496 | #canvas.add_events(Gdk::EventMask::BUTTON_PRESS_MASK.nick) #This points to a nickname, basically a string like "button-press-mask" in this case
497 |
498 | #Accel groups, parameter 2 is the modifier
499 | # 0 - no modifier
500 | # 1 - shift
501 | # 2 - no modifier?
502 | # 3 - shift again?
503 | # 4 - Cntl
504 | # 5 - Cntl+Shift
505 |
506 | file_chooser.filter = Gtk::FileFilter.new
507 | file_chooser.filter.add_pattern("*.nous")
508 | file_chooser.filter.name = "nous file filter"
509 | end
510 | def confirm(type)
511 | case type
512 | when "new"
513 | @current_window = "new_confirm"
514 | confirmer_confirm.label = "Create New"
515 | confirmer_label.markup = " There are unsaved changes. \n Do you wish to proceed? "
516 | confirmer_cancel.visible = true
517 | when "quit"
518 | @current_window = "quit_confirm"
519 | confirmer_confirm.label = "Quit"
520 | confirmer_cancel.visible = true
521 | confirmer_label.markup = " There are unsaved changes. \n Do you wish to proceed? "
522 | when "path_warning"
523 | @current_window = "path_warning"
524 | confirmer_confirm.label = "OK"
525 | confirmer_cancel.visible = false
526 | confirmer_label.markup = " WARNING: Live composition detected. \n This may have affected round-robin point starting paths. "
527 | end
528 | confirmer.visible = true
529 | end
530 | def file_input_check(type)
531 | case type
532 | when "chooser"
533 | unless UI::file_chooser.uri == nil
534 | UI::file_name.text = UI::file_chooser.uri.split("/").last
535 | else
536 | UI::file_name.text = ""
537 | end
538 | when "name"
539 | unless UI::file_name.text != "" ||
540 | UI::file_chooser.uri != nil ||
541 | UI::file_name.text.count(".") > 1
542 | then
543 | UI::file_operation.sensitive = false
544 | else UI::file_operation.sensitive = true
545 | end
546 | end
547 | end
548 | def file_oper(type)
549 | case type
550 | when "open"
551 | @current_window = "file_open"
552 | file_chooser.title = "Open"
553 | file_operation.label = "Open"
554 | file_chooser.visible = true
555 | file_name.editable = false
556 | when "save"
557 | @current_window = "file_save"
558 | if @current_file == nil #If the working file does not exist yet
559 | file_chooser.title = "Save As"
560 | file_operation.label = "Save As"
561 | file_name.editable = true
562 | file_chooser.visible = true
563 | else
564 | save_operation #Resave the file if @current_file already exists
565 | end
566 | when "saveas"
567 | @current_window = "file_save"
568 | #If the working file does not exist yet, suggest a name
569 | file_name.text = @current_file.split("\\").last unless @current_file == nil
570 | file_chooser.title = "Save As"
571 | file_operation.label = "Save As"
572 | file_name.editable = true
573 | file_chooser.visible = true
574 | end
575 | end
576 | def confirm_act(choice)
577 | if choice == "yes"
578 | case @current_window
579 | when "new_confirm"
580 | CC.nouspoints.each do |n|
581 | n.traveler_start = false
582 | n.path_to = []
583 | n.path_from = []
584 | end
585 | CC.nouspoints = []
586 | file_name.text = ""
587 | CC.tempo = 120
588 | CC.scale = "Chromatic"
589 | CC.root_note = 60
590 | CC.beats = 4
591 | CC.beat_note = 4
592 |
593 | CC.set_tempo(CC.tempo)
594 | CC.set_scale(CC.scale,CC.root_note)
595 | CC.canvas_grid_change(nil)
596 | tempo_adj.value = CC.tempo
597 | @scale_iters.each {|s| scale_combo.active_iter = s if s[0] == CC.scale}
598 | root_adj.value = CC.root_note
599 | @current_file = nil
600 | midinous.title = "Midinous"
601 | when "quit_confirm"
602 | Gtk.main_quit
603 | end
604 | end
605 | confirmer.visible = false
606 | canvas.queue_draw
607 | end
608 | def file_oper_act(choice)
609 | if choice == "yes"
610 | case @current_window
611 | when "file_open"
612 | #Remove current points and load points from file
613 | load_operation
614 | when "file_save"
615 | #Save nouspoints to a new, or existing file, window only if new
616 | save_operation
617 | end
618 | end
619 | file_chooser.visible = false
620 | canvas.queue_draw
621 | end
622 |
623 | def save_operation
624 |
625 | unless file_name.text[-5..-1] == ".nous"
626 | @current_file = "#{file_chooser.current_folder_uri}/#{file_name.text}.nous"
627 |
628 | else @current_file = "#{file_chooser.current_folder_uri}/#{file_name.text}"
629 | end
630 |
631 | operator = @current_file.sub("file:///","")
632 | operator = operator.gsub("/","\\")
633 | operator = operator.gsub("%20"," ")
634 |
635 | IO.binwrite(operator, "")
636 | save = File.open(operator, "a")
637 | issued_id = 0
638 | CC.nouspoints.each do |n|
639 | n.save_id = issued_id
640 | issued_id += 1
641 | end
642 | CC.nouspoints.each do |n|
643 | n.write_props(save)
644 | save.write("\n")
645 | end
646 | save.write("#{CC.tempo}<~>")
647 | save.write("#{CC.scale}<~>")
648 | save.write("#{CC.root_note}<~>")
649 | save.write("#{CC.beats}<~>")
650 | save.write("#{CC.beat_note}")
651 | midinous.title = "Midinous - #{operator}"
652 | save.close
653 | end
654 | def load_operation
655 | CC.canvas_stop
656 | CC.nouspoints = []
657 | @current_file = file_chooser.uri
658 | operator = @current_file.sub("file:///","")
659 | operator = operator.gsub("/","\\")
660 | operator = operator.gsub("%20"," ")
661 |
662 | load = IO.binread(operator)
663 | load = load.gsub("\r","")
664 | load = load.split("\n")
665 | load[0..-2].each do |point_line|
666 | point_line = point_line.split("<~>")
667 | CC.nouspoints << NousPoint.new(eval(point_line[1]),point_line[0].to_i)
668 | end
669 | CC.nouspoints.each do |n|
670 | load.each do |point_line|
671 | point_line = point_line.split("<~>")
672 | if point_line[0].to_i == n.save_id
673 | if point_line[2].match(/^([+]|-)[0-9]{1,2}$/)
674 | n.note = point_line[2]
675 | n.use_rel = true
676 | elsif (point_line[2].to_i >= 0 &&
677 | point_line[2].to_i <= 127 &&
678 | !(point_line[2].include?("+") || point_line[2].include?("-")))
679 | then
680 | n.note = point_line[2].to_i
681 | end
682 | n.velocity = point_line[3].to_i
683 | n.channel = point_line[4].to_i
684 | n.duration = point_line[5].to_i
685 | n.set_default_color(hex_to_color(point_line[6]))
686 | n.repeat = point_line[7].to_i
687 | n.play_modes = eval(point_line[8])
688 | n.traveler_start = eval(point_line[9])
689 | n.use_rel = eval(point_line[10])
690 | n.path_mode = point_line[11]
691 | n.mute = eval(point_line[12])
692 | n.path_to_rels = eval(point_line[13])
693 | n.path_from_rels = eval(point_line[14])
694 | n.path_to_rels.each {|ptr| n.path_to << CC.nouspoints.find {|f| ptr == f.save_id}}
695 | n.path_from_rels.each {|pfr| n.path_from << CC.nouspoints.find {|f| pfr == f.save_id}}
696 | end
697 | end
698 | end
699 | load_last = load.last.split("<~>")
700 | CC.tempo = load_last[0].to_f
701 | CC.scale = load_last[1]
702 | CC.root_note = load_last[2].to_i
703 | CC.beats = load_last[3].to_i
704 | CC.beat_note = load_last[4].to_i
705 |
706 | CC.set_tempo(CC.tempo)
707 | CC.set_scale(CC.scale,CC.root_note)
708 | CC.canvas_grid_change(nil)
709 | tempo_adj.value = CC.tempo
710 | @scale_iters.each {|s| scale_combo.active_iter = s if s[0] == CC.scale}
711 | root_adj.value = CC.root_note
712 |
713 | midinous.title = "Midinous - #{operator}"
714 | end
715 | end
716 |
717 | class Tool
718 | attr_reader :tool_id
719 | def initialize
720 | @tool_id = 1
721 | end
722 | def set_tool(id)
723 | @tool_id = id
724 | unless @tool_id == 4
725 | UI::path_builder.sensitive = false
726 | end
727 | case
728 | when @tool_id == 1
729 | UI::main_tool_1.active = true
730 | UI::tool_descrip.text = "Select"
731 | UI::canvas.queue_draw
732 | when @tool_id == 2
733 | UI::main_tool_2.active = true
734 | UI::tool_descrip.text = "Place"
735 | UI::canvas.queue_draw
736 | when @tool_id == 3
737 | UI::main_tool_3.active = true
738 | UI::tool_descrip.text = "Move"
739 | UI::canvas.queue_draw
740 | when @tool_id == 4
741 | UI::main_tool_4.active = true
742 | UI::path_builder.sensitive = true
743 | UI::tool_descrip.text = "Path"
744 | UI::canvas.queue_draw
745 | end
746 | end
747 |
748 | end
749 |
750 | UI = UI_Elements.new() #Create a new UI_Elements object
751 | UI::build_ui
752 | Active_Tool = Tool.new
--------------------------------------------------------------------------------
/lib/midinous/points.rb:
--------------------------------------------------------------------------------
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 .
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 | "Mute",
33 | "Play Mode"]
34 | @prop_names_multi = ["Note",
35 | "Velocity",
36 | "Duration (beats)",
37 | "Channel",
38 | "Repeat",
39 | "Color",
40 | "Signal Start",
41 | "Mute",
42 | "Play Mode"]
43 | @prop_names_adv = []
44 | @prop_names_adv_multi = []
45 | @curr_prop = nil
46 | @curr_prop_adv = nil
47 | @mempoints = []
48 | @mempointsbuff = []
49 | @copy_pos = []
50 | @copy_type = nil
51 | @paste_count = 0
52 | end
53 |
54 | def add_point(r_origin,points) #Point existence search
55 | unless (collision_check(r_origin,points))
56 | points << NousPoint.new(r_origin,-1)
57 | end
58 | return points
59 | end
60 |
61 | def add_path(points)
62 | points.find_all { |n| n.pathable && !n.source}.each do |t|
63 | points.find(&:source).path_to << t
64 | t.path_from << points.find(&:source)
65 | end
66 | return points
67 | end
68 |
69 | def set_path_mode(mode)
70 | CC.nouspoints.find_all(&:selected).each {|n| n.path_mode = mode}
71 | UI::canvas.queue_draw
72 | end
73 | def set_note_via_devc(note)
74 | CC.nouspoints.find_all(&:selected).each {|n| n.note = note}
75 | Pm.note_send(Pm.in_chan,note,100)
76 | GLib::Timeout.add(100) do
77 | Pm.note_rlse(1,note)
78 | false
79 | end
80 | populate_prop(CC.nouspoints)
81 | end
82 | def inc_note(inc)
83 | CC.nouspoints.find_all(&:selected).each do |n|
84 | signed = n.note
85 | val = n.note.to_i
86 | val += inc
87 | if val >= 0
88 | n.note = "+#{val}"
89 | else n.note = val
90 | end
91 | if !(signed.to_s.include?("+") || signed.to_s.include?("-"))
92 | n.note = n.note.to_i
93 | n.note = n.note.clamp(0,127)
94 | end
95 | end
96 | populate_prop(CC.nouspoints)
97 | end
98 |
99 | def collision_check(r_origin,points)
100 | return true if points.any? { |n| r_origin == n.origin }
101 | end
102 |
103 | def select_points(box,points) #Select points with the select tool
104 | UI::prop_list_model.clear
105 | UI::prop_mod.text = ""
106 | box_origin = [box[0],box[1]] #box is an array with 4 values
107 | points.each do |n|
108 | if check_bounds(n.origin,box)
109 | n.select
110 | elsif check_bounds(box_origin,n.bounds)
111 | n.select
112 | else
113 | n.deselect
114 | UI::prop_list_model.clear
115 | UI::prop_mod.text = ""
116 | end
117 | end
118 | populate_prop(points)
119 | return points
120 | end
121 |
122 | def select_all
123 | CC.nouspoints.each {|n| n.selected = true} unless CC.nouspoints == []
124 | populate_prop(CC.nouspoints)
125 | UI::canvas.queue_draw
126 | end
127 | def copy_points (type)
128 | return if CC.nouspoints.empty?
129 | origins = []
130 | CC.nouspoints.find_all(&:selected).each {|n| origins << n.origin}
131 | @copy_pos = origins.min
132 | @copy_type = type
133 | @mempointsbuff = CC.nouspoints.find_all(&:selected)
134 | @paste_count = 0
135 | end
136 | def paste_points
137 | return if @mempointsbuff.empty?
138 | copy_lookup = {}
139 |
140 | # Clone the cut copy of pasted points if someone wants to cutpaste more than once
141 | # Then turn the cut operation into the copy operation
142 | copy_points("copy") if @paste_count == 1
143 |
144 | # Clone the points and track old point => new point
145 | @mempointsbuff.each do |n|
146 | n.selected = false if @copy_type == "copy"
147 | mem_point = n.clone
148 | @mempoints << mem_point
149 | copy_lookup[n.object_id] = mem_point
150 | end
151 |
152 | # Update the point relations
153 | @mempoints.each do |n|
154 | n.path_to = n.path_to.map { |pt| copy_lookup[pt.object_id] }.compact
155 | n.path_from = n.path_from.map { |pf| copy_lookup[pf.object_id] }.compact
156 | end
157 |
158 | @paste_count = 0 if @copy_type == "copy"
159 | delete_points if @copy_type == "cut" && @paste_count == 0
160 | @paste_count += 1
161 |
162 | paste_pos = CC.mouse_last_pos
163 | diff = round_to_grid([paste_pos[0] - @copy_pos[0],paste_pos[1] - @copy_pos[1]])
164 | @mempoints.each do |m|
165 | m.set_destination(diff)
166 | m.selected = true
167 | end
168 | @mempoints.each {|m| CC.nouspoints << m} if paste_check(diff,@mempoints)
169 | @mempoints = []
170 |
171 | populate_prop(CC.nouspoints)
172 | UI::canvas.queue_draw
173 | end
174 | def paste_check(diff,memp)
175 | memp.each {|m| return false if CC.nouspoints.any? { |n| n.origin == m.origin}}
176 | return true
177 | end
178 |
179 | def populate_prop (points)
180 | UI::prop_list_model.clear
181 | UI::prop_mod.text = ""
182 | point = nil
183 | point = points.find(&:selected) if points.find_all(&:selected).length == 1
184 | if point
185 | prop_vals = [point.note,
186 | point.velocity,
187 | point.duration,
188 | point.channel,
189 | point.repeat,
190 | point.x,
191 | point.y,
192 | color_to_hex(point.default_color),
193 | point.path_mode,
194 | point.traveler_start,
195 | point.mute,
196 | point.play_modes[0]]
197 | @prop_names.each do |v|
198 | iter = UI::prop_list_model.append
199 | iter[0] = v
200 | iter[1] = prop_vals[@prop_names.find_index(v)].to_s
201 | end
202 | elsif points.find_all(&:selected).length > 1
203 | @prop_names_multi.each do |v|
204 | equalizer = []
205 | iter = UI::prop_list_model.append
206 | iter[0] = v
207 | case v
208 | when "Note"
209 | points.find_all(&:selected).each {|p| equalizer << p.note}
210 | when "Velocity"
211 | points.find_all(&:selected).each {|p| equalizer << p.velocity}
212 | when "Duration (beats)"
213 | points.find_all(&:selected).each {|p| equalizer << p.duration}
214 | when "Channel"
215 | points.find_all(&:selected).each {|p| equalizer << p.channel}
216 | when "Color"
217 | points.find_all(&:selected).each {|p| equalizer << color_to_hex(p.default_color)}
218 | when "Signal Start"
219 | points.find_all(&:selected).each {|p| equalizer << p.traveler_start}
220 | when "Mute"
221 | points.find_all(&:selected).each {|p| equalizer << p.mute}
222 | when "Play Mode"
223 | points.find_all(&:selected).each {|p| equalizer << p.play_modes[0]}
224 | when "Repeat"
225 | points.find_all(&:selected).each {|p| equalizer << p.repeat}
226 | end
227 | if equalizer.uniq.count == 1
228 | iter[1] = equalizer[0].to_s
229 | else iter[1] = "Multiple Values"
230 | end
231 | end
232 | else
233 | UI::prop_list_model.clear
234 | UI::prop_mod.text = ""
235 | end
236 |
237 | end
238 | def prop_list_select(selected)
239 | return if selected == nil
240 | @curr_prop = selected[0]
241 | if selected[1][0] == "#"
242 | UI::prop_mod.text = selected[1][1..6]
243 | else UI::prop_mod.text = selected[1]
244 | end
245 | UI::prop_mod.position = 0
246 | UI::prop_mod.grab_focus
247 | end
248 |
249 | def check_input(text)
250 | play_modes = ["robin","split","portal","random"]
251 | path_modes = ["horz","vert"]
252 | signal_states = ["true","false"]
253 | case @curr_prop
254 | when "Note"
255 | if (text.to_i >= 1 && text.to_i <= 127) || (text.match(/^([+]|-)[0-9]{1,2}$/))
256 | UI::prop_mod_button.sensitive = true
257 | else UI::prop_mod_button.sensitive = false
258 | end
259 | when "Velocity"
260 | if (text.to_i >= 1 && text.to_i <= 127)
261 | UI::prop_mod_button.sensitive = true
262 | else UI::prop_mod_button.sensitive = false
263 | end
264 | when "Duration (beats)"
265 | if text.to_i >= 1 && text.to_i <= 1000
266 | UI::prop_mod_button.sensitive = true
267 | else UI::prop_mod_button.sensitive = false
268 | end
269 | when "Channel"
270 | if text.to_i >= 1 && text.to_i <= 16
271 | UI::prop_mod_button.sensitive = true
272 | else UI::prop_mod_button.sensitive = false
273 | end
274 | when "X-coordinate", "Y-coordinate"
275 | if text.to_i >= CC.grid_spacing &&
276 | text.to_i <= (CANVAS_SIZE - CC.grid_spacing)
277 | then
278 | UI::prop_mod_button.sensitive = true
279 | else UI::prop_mod_button.sensitive = false
280 | end
281 | when "Color"
282 | if text.match(/^[0-9A-Fa-f]{6}$/)
283 | UI::prop_mod_button.sensitive = true
284 | else UI::prop_mod_button.sensitive = false
285 | end
286 | when "Path Mode"
287 | if path_modes.include? text
288 | UI::prop_mod_button.sensitive = true
289 | else UI::prop_mod_button.sensitive = false
290 | end
291 | when "Signal Start","Mute"
292 | if signal_states.include? text
293 | UI::prop_mod_button.sensitive = true
294 | else UI::prop_mod_button.sensitive = false
295 | end
296 | when "Play Mode"
297 | if play_modes.include? text
298 | UI::prop_mod_button.sensitive = true
299 | else UI::prop_mod_button.sensitive = false
300 | end
301 | when "Repeat"
302 | if text.to_i >= 0 && text.to_i <= 128
303 | UI::prop_mod_button.sensitive = true
304 | else UI::prop_mod_button.sensitive = false
305 | end
306 | else UI::prop_mod_button.sensitive = false
307 | end
308 | end
309 |
310 | def modify_properties(points)
311 | case @curr_prop
312 | when "Note"
313 | points.find_all(&:selected).each do |p|
314 | if UI::prop_mod.text.match(/^([+]|-)[0-9]{1,2}$/)
315 | unless p.traveler_start == true
316 | p.note = UI::prop_mod.text
317 | p.use_rel = true
318 | end
319 | elsif (UI::prop_mod.text.to_i >= 0 &&
320 | UI::prop_mod.text.to_i <= 127 &&
321 | !(UI::prop_mod.text.include?("+") || UI::prop_mod.text.include?("-")))
322 | then
323 | p.note = UI::prop_mod.text.to_i
324 | p.use_rel = false
325 | end
326 | end
327 | when "Velocity"
328 | points.find_all(&:selected).each {|p| p.velocity = UI::prop_mod.text.to_i}
329 | when "Duration (beats)"
330 | points.find_all(&:selected).each {|p| p.duration = UI::prop_mod.text.to_i}
331 | when "Channel"
332 | points.find_all(&:selected).each {|p| p.channel = UI::prop_mod.text.to_i}
333 | when "X-coordinate"
334 | temp_origin = points.find(&:selected).origin
335 | points.find(&:selected).origin = [round_num_to_grid(UI::prop_mod.text.to_i),temp_origin[1]]
336 | when "Y-coordinate"
337 | temp_origin = points.find(&:selected).origin
338 | points.find(&:selected).origin = [temp_origin[0],round_num_to_grid(UI::prop_mod.text.to_i)]
339 | when "Color"
340 | points.find_all(&:selected).each {|p| p.set_default_color(hex_to_color("##{UI::prop_mod.text}"))}
341 | when "Path Mode"
342 | points.find(&:selected).path_mode = UI::prop_mod.text
343 | when "Signal Start"
344 | case UI::prop_mod.text
345 | when "true"
346 | points.find_all(&:selected).each {|p| p.traveler_start = true}
347 | when "false"
348 | points.find_all(&:selected).each {|p| p.traveler_start = false}
349 | end
350 | when "Mute"
351 | case UI::prop_mod.text
352 | when "true"
353 | points.find_all(&:selected).each {|p| p.mute = true}
354 | when "false"
355 | points.find_all(&:selected).each {|p| p.mute = false}
356 | end
357 | when "Play Mode"
358 | if UI::prop_mod.text == "robin" || UI::prop_mod.text == "portal"
359 | points.find_all(&:selected).each {|p| p.play_modes.rotate! until p.play_modes[0] == UI::prop_mod.text}
360 | else
361 | 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}
362 | end
363 | when "Repeat"
364 | points.find_all(&:selected).each do |p|
365 | p.repeat = UI::prop_mod.text.to_i
366 | end
367 | end
368 | return points
369 | end
370 |
371 | def select_path_point(origin,points,source_chosen)
372 | points.find_all {|g| check_bounds(origin,g.bounds)}.each do |n|
373 | case !n.pathable
374 | when true #If clicking where a non-pathable point is
375 | source_chosen = n.path_set(source_chosen)
376 | when false
377 | if n.source
378 | points, source_chosen = cancel_path(points)
379 | end
380 | n.path_unset
381 | end
382 | end
383 | return points, source_chosen
384 | end
385 |
386 | def play_mode_rotate(dir)
387 | CC.nouspoints.find_all(&:selected).each do |n|
388 | if n.path_to.length <= 1
389 | case n.play_modes[0]
390 | when "robin"
391 | n.play_modes.rotate!(dir) until n.play_modes[0] == "portal"
392 | when "portal"
393 | n.play_modes.rotate!(dir) until n.play_modes[0] == "robin"
394 | when "random","split"
395 | n.play_modes.rotate!(dir) until n.play_modes[0] == "robin"
396 | end
397 | else
398 | n.play_modes.rotate!(dir)
399 | end
400 | populate_prop(CC.nouspoints.find_all)
401 | UI::canvas.queue_draw
402 | end
403 | end
404 | def play_mode_set(point,mode)
405 | return unless ["robin","portal","split","random"].find {|f| f == mode}
406 | point.play_modes.rotate!(1) until point.play_modes[0] == mode
407 | end
408 | def path_rotate(dir)
409 | CC.nouspoints.find_all(&:selected).each do |n|
410 | case dir
411 | when "+"
412 | n.path_to.rotate!(1)
413 | when "-"
414 | n.path_to.rotate!(-1)
415 | end
416 | end
417 |
418 | UI::canvas.queue_draw
419 | end
420 |
421 | def cancel_selected(points)
422 | points.find_all(&:selected).each { |n| n.deselect }
423 | return points
424 | end
425 | def cancel_path(points)
426 | points.find_all(&:pathable).each { |n| n.path_unset }
427 | return points, false
428 | end
429 |
430 | def mute_points
431 | CC.nouspoints.find_all(&:selected).each do |n|
432 | case n.mute
433 | when true
434 | n.mute = false
435 | when false
436 | n.mute = true
437 | end
438 | UI::canvas.queue_draw
439 | end
440 | end
441 | def delete_points
442 | delete_paths_to
443 | delete_paths_from
444 | CC.nouspoints.reject!(&:selected)
445 | UI::prop_list_model.clear
446 | UI::prop_mod.text = ""
447 | UI::canvas.queue_draw
448 | end
449 | def delete_paths_to
450 | CC.nouspoints.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 }}}
451 | CC.nouspoints.find_all {|f| !f.path_to.length.zero? && f.selected == true}.each do |n|
452 | n.path_to = []
453 | play_mode_set(n,"robin")
454 | end
455 | UI::canvas.queue_draw
456 | end
457 | def delete_paths_from
458 | CC.nouspoints.find_all {|f| !f.path_from.length.zero? && f.selected == true}.each do |n|
459 | n.path_from.each do |b|
460 | b.path_to.reject! {|g| g == n }
461 | play_mode_set(b,"robin") if b.path_to.length <= 1
462 | end
463 | end
464 | CC.nouspoints.find_all {|f| !f.path_from.length.zero? && f.selected == true}.each {|n| n.path_from = []}
465 | UI::canvas.queue_draw
466 | end
467 |
468 | def move_points(diff,points)
469 | if move_check(diff,points)
470 | points.find_all(&:selected).each {|n| n.set_destination(diff) }
471 | populate_prop(points)
472 | end
473 | return points
474 | end
475 | def move_check(diff,points)
476 | points.find_all(&:selected).each do |n|
477 | dest = n.origin.map
478 | dest = dest.to_a
479 | dest.map! {|g| g += diff[dest.find_index(g)]}
480 | return false if points.find_all(&:not_selected).any? { |g| g.origin == dest}
481 | end
482 | return true
483 | end
484 |
485 | def set_start
486 | CC.nouspoints.find_all(&:selected).each do |n|
487 | if n.traveler_start == false
488 | n.traveler_start = true
489 | n.note = CC.root_note if n.note.to_s.match(/^([+]|-)[0-9]{1,2}$/)
490 | elsif n.traveler_start == true
491 | n.traveler_start = false
492 | end
493 | end
494 | UI::canvas.queue_draw
495 | populate_prop(CC.nouspoints)
496 | end
497 |
498 | end
499 |
500 | class NousPoint
501 | include Logic_Controls
502 | attr_accessor :source, :color, :path_to, :path_from, :note, :x, :y,
503 | :velocity, :duration, :default_color, :path_mode,
504 | :traveler_start, :channel, :playing, :play_modes,
505 | :path_to_memory, :repeat, :repeating, :mute,
506 | :use_rel, :selected, :save_id, :path_to_rels, :path_from_rels
507 | attr_reader :pathable, :origin, :bounds
508 |
509 | def initialize(o,save_id) #where the point was initially placed, and a unique identifier
510 | @dp = [4,8,10,12,14,16,20]
511 |
512 | @x = o[0]
513 | @y = o[1]
514 | @origin = o
515 | @bounds = [@x-@dp[5],@y-@dp[5],@x+@dp[5],@y+@dp[5]]
516 | @color = GREY #point color defaults to gray++
517 | @path_color = CYAN
518 | @default_color = GREY
519 | @note = 60 #all notes start at middle c (C3), can be a note or a reference
520 | @velocity = 100 # `` with 100 velocity
521 | @channel = 1 # `` assigned to midi channel 1 (instrument 1, but we will refer to them as channels, not instruments)
522 | @duration = 1 #length of note in grid points (should be considered beats)
523 | @repeat = 0 #Number of times the node should repeat before moving on
524 | @save_id = save_id
525 | @play_modes = ["robin","split","portal","random"]
526 | @traveler_start = false
527 | @playing = false
528 | @pathable = false
529 | @selected = false
530 | @source = false
531 | @repeating = false
532 | @use_rel = false
533 | @mute = false
534 | @path_to = [] #array of references to points that are receiving a path from this point
535 | @path_to_memory = [] #memory of @path_to so that it can be reset upon stopping.
536 | @path_to_rels = []
537 | @path_from_rels = []
538 | @path_from = [] #array of references to points that are sending a path to this point
539 | @path_mode = "horz"
540 | @chev_offsets = [0,0,0,0]
541 | end
542 |
543 | def set_rels
544 | @path_to_rels = []
545 | @path_from_rels = []
546 | path_to.each {|pt| @path_to_rels << pt.save_id}
547 | path_from.each {|pf| @path_from_rels << pf.save_id}
548 | end
549 |
550 | def write_props(file)
551 | set_rels
552 | file.write("#{@save_id}<~>")
553 | file.write("#{@origin}<~>")
554 | file.write("#{@note}<~>")
555 | file.write("#{@velocity}<~>")
556 | file.write("#{@channel}<~>")
557 | file.write("#{@duration}<~>")
558 | file.write("#{color_to_hex(@default_color)}<~>")
559 | file.write("#{@repeat}<~>")
560 | file.write("#{@play_modes}<~>")
561 | file.write("#{@traveler_start}<~>")
562 | file.write("#{@use_rel}<~>")
563 | file.write("#{@path_mode}<~>")
564 | file.write("#{@mute}<~>")
565 | file.write("#{@path_to_rels}<~>")
566 | file.write("#{@path_from_rels}")
567 | end
568 |
569 | def not_selected
570 | !@selected
571 | end
572 | def not_pathable
573 | !@pathable
574 | end
575 |
576 | def origin=(o) #sets the origin of the point explicitly
577 | @x = o[0]
578 | @y = o[1]
579 | @origin = o
580 | @bounds = [@x-@dp[5],@y-@dp[5],@x+@dp[5],@y+@dp[5]]
581 | end
582 |
583 | def path_set(source_chosen)
584 | @pathable = true
585 | case source_chosen
586 | when false #if source point was not chosen (first point clicked on path screen)
587 | @source = true #Path source is now chosen on this node
588 | @color = CYAN
589 | return true
590 | when true #Path source is already chosen in this operation
591 | @color = GREEN
592 | end
593 | return source_chosen
594 | end
595 | def path_unset
596 | @pathable = false
597 | @source = false
598 | @color = @default_color
599 | end
600 |
601 | def reset_path_to
602 | if @path_to.length != @path_to_memory.length
603 | @path_to_memory = []
604 | UI.confirm("path_warning")
605 | else
606 | @path_to = []
607 | @path_to_memory.each {|p| @path_to << p}
608 | @path_to_memory = []
609 | end
610 | end
611 |
612 | def set_default_color(c)
613 | @color = c
614 | @default_color = c
615 | end
616 | def set_path_color
617 | case @play_modes[0]
618 | when "robin","split"
619 | @path_color = CYAN
620 | when "portal"
621 | @path_color = RED
622 | when "random"
623 | @path_color = VLET
624 | end
625 | end
626 | def set_destination(diff) #sets a new origin for the point based on x,y coordinate differences
627 | @x += diff[0]
628 | @y += diff[1]
629 | @origin = [@x,@y]
630 | @bounds = [@x-@dp[5],@y-@dp[5],@x+@dp[5],@y+@dp[5]]
631 | end
632 | def select #elevate color to denote 'selected' and sets a flag
633 | @selected = true
634 | end
635 | def deselect #resets the color from elevated 'selected' values and sets a flag
636 | @selected = false
637 | @color = @default_color
638 | end
639 |
640 | def draw(cr) #point will always be drawn to this specification.
641 |
642 | cr.set_source_rgba(@color[0],@color[1],@color[2],0.4)
643 |
644 | if @traveler_start
645 | traveler_start_draw(cr)
646 | else
647 | cr.rounded_rectangle(@x-@dp[1],@y-@dp[1],@dp[5],@dp[5],2,2) #slightly smaller rectangle adds 'relief' effect
648 | end
649 | cr.fill
650 |
651 | cr.set_source_rgba(@color[0],@color[1],@color[2],1)
652 | case @play_modes[0]
653 | when "robin"
654 | if @path_to.length > 1
655 | cr.move_to(@x-8,@y)
656 | cr.line_to(@x+6,@y-9)
657 | cr.set_line_width(2)
658 | cr.stroke
659 | cr.move_to(@x-8,@y)
660 | cr.set_dash([1,4],0)
661 | cr.line_to(@x+6,@y+9)
662 | cr.set_line_width(2)
663 | cr.stroke
664 | cr.set_dash([],0)
665 | else
666 | cr.circle(@x,@y,1)
667 | cr.fill
668 | end
669 | when "split"
670 | cr.move_to(@x-8,@y)
671 | cr.line_to(@x+6,@y-9)
672 | cr.move_to(@x-8,@y)
673 | cr.line_to(@x+6,@y+9)
674 | cr.set_line_width(2)
675 | cr.stroke
676 | when "portal"
677 | cr.circle(@x,@y,6)
678 | cr.set_line_width(2)
679 | cr.stroke
680 | when "random"
681 | cr.rectangle(@x-6,@y-2,8,8)
682 | cr.rectangle(@x-2,@y-6,8,8)
683 | cr.set_line_width(2)
684 | cr.stroke
685 | end
686 |
687 | if @repeat > 0
688 | if @traveler_start
689 | cr.move_to(@x+3,@y-@dp[2]-2)
690 | cr.line_to(@x-2,-6+@y-@dp[2]-2)
691 | cr.line_to(@x-2,6+@y-@dp[2]-2)
692 | cr.line_to(@x+3,@y-@dp[2]-2)
693 | cr.fill
694 | cr.move_to(@x-3,@y+@dp[2]+2)
695 | cr.line_to(@x+2,-6+@y+@dp[2]+2)
696 | cr.line_to(@x+2,6+@y+@dp[2]+2)
697 | cr.line_to(@x-3,@y+@dp[2]+2)
698 | cr.fill
699 | else
700 | cr.move_to(@x-2,@y-@dp[2])
701 | cr.line_to(@x-7,-6+@y-@dp[2])
702 | cr.line_to(@x-7,6+@y-@dp[2])
703 | cr.line_to(@x-2,@y-@dp[2])
704 | cr.fill
705 | cr.move_to(@x+2,@y+@dp[2])
706 | cr.line_to(@x+7,-6+@y+@dp[2])
707 | cr.line_to(@x+7,6+@y+@dp[2])
708 | cr.line_to(@x+2,@y+@dp[2])
709 | cr.fill
710 | end
711 | end
712 |
713 | cr.set_dash([2,4],0) if @mute
714 | if !@selected
715 | if @traveler_start
716 | traveler_start_draw(cr)
717 | else
718 | cr.rounded_rectangle(@x-@dp[2],@y-@dp[2],@dp[6],@dp[6],2,2)
719 | end
720 | end
721 | if @selected
722 | cr.set_source_rgba(1,1,1,0.8)
723 | if @traveler_start
724 | traveler_start_draw(cr)
725 | else
726 | cr.rounded_rectangle(@x-@dp[2],@y-@dp[2],@dp[6],@dp[6],2,2) #a slightly smaller rectangle adds 'relief' effect
727 | end
728 | selection_caret_draw(cr)
729 | end
730 | cr.set_line_width(2)
731 | cr.stroke
732 | cr.set_dash([],0)
733 | play_draw(cr) if @playing
734 | repeat_draw(cr) if @repeating
735 | end
736 |
737 | def path_draw(cr)
738 | if !@selected
739 |
740 | cr.set_source_rgba(@path_color[0],@path_color[1],@path_color[2],0.6)
741 | @path_to.each {|t| trace_path_to(cr,t)}
742 |
743 | elsif @selected
744 |
745 | cr.set_source_rgba(LGRN[0],LGRN[1],LGRN[2],0.8)
746 | @path_to.each {|t| trace_path_to(cr,t)}
747 | cr.set_line_cap(1) #Round
748 | cr.set_line_join(2) #Miter
749 | cr.set_line_width(3)
750 | cr.stroke
751 |
752 | end
753 | end
754 | def caret_draw(cr)
755 | cr.set_dash([],0)
756 | @chev_offsets = [0,0,0,0]
757 | @path_from.each do |s|
758 | input_mark_draw(cr,relative_pos(@x-s.x,@y-s.y),s)
759 | cr.set_source_rgba(s.color[0],s.color[1],s.color[2],1)
760 | cr.fill
761 | end
762 | end
763 | def play_draw(cr) #If a note is playing, show a visual indicator
764 | cr.set_source_rgb(@color[0],@color[1],@color[2])
765 | if @traveler_start
766 | traveler_start_draw(cr)
767 | else
768 | cr.rounded_rectangle(@x-@dp[2],@y-@dp[2],@dp[6],@dp[6],2,2)
769 | end
770 | cr.fill
771 | end
772 | def repeat_draw(cr)
773 | cr.set_source_rgb(@color[0],@color[1],@color[2])
774 | cr.rounded_rectangle(@x-@dp[2]+3,@y-@dp[2]+3,@dp[6]-6,@dp[6]-6,2,2)
775 | cr.fill
776 | end
777 |
778 | def traveler_start_draw(cr) #Shape of a traveler start position
779 | cr.move_to(@x-@dp[0],@y-@dp[3])
780 | cr.line_to(@x+@dp[0],@y-@dp[3])
781 | cr.line_to(@x+@dp[1],@y-@dp[1])
782 | cr.line_to(@x+@dp[1],@y+@dp[1])
783 | cr.line_to(@x+@dp[0],@y+@dp[3])
784 | cr.line_to(@x-@dp[0],@y+@dp[3])
785 | cr.line_to(@x-@dp[1],@y+@dp[1])
786 | cr.line_to(@x-@dp[1],@y-@dp[1])
787 | cr.line_to(@x-@dp[0],@y-@dp[3])
788 | end
789 | def selection_caret_draw(cr) #Shape of a selection caret
790 | cr.move_to(@x-@dp[4],@y-@dp[4])
791 | cr.line_to(@x-@dp[2],@y-@dp[4])
792 | cr.move_to(@x-@dp[4],@y-@dp[4])
793 | cr.line_to(@x-@dp[4],@y-@dp[2])
794 |
795 | cr.move_to(@x+@dp[4],@y-@dp[4])
796 | cr.line_to(@x+@dp[2],@y-@dp[4])
797 | cr.move_to(@x+@dp[4],@y-@dp[4])
798 | cr.line_to(@x+@dp[4],@y-@dp[2])
799 |
800 | cr.move_to(@x-@dp[4],@y+@dp[4])
801 | cr.line_to(@x-@dp[2],@y+@dp[4])
802 | cr.move_to(@x-@dp[4],@y+@dp[4])
803 | cr.line_to(@x-@dp[4],@y+@dp[2])
804 |
805 | cr.move_to(@x+@dp[4],@y+@dp[4])
806 | cr.line_to(@x+@dp[2],@y+@dp[4])
807 | cr.move_to(@x+@dp[4],@y+@dp[4])
808 | cr.line_to(@x+@dp[4],@y+@dp[2])
809 | end
810 | def trace_path_to(cr,t)
811 |
812 | case @play_modes[0]
813 | when "robin"
814 | cr.set_dash([5,5],0)
815 | if @path_to[0] == t
816 | cr.set_dash([],0)
817 | end
818 | when "portal"
819 | cr.set_dash([1,5],0)
820 | end
821 |
822 | rel_pos = relative_pos(t.x-@x,t.y-@y)
823 | case rel_pos
824 | when "n"
825 | cr.move_to(@x,@y-10)
826 | cr.line_to(t.x,t.y+10)
827 | when "s"
828 | cr.move_to(@x,@y+10)
829 | cr.line_to(t.x,t.y-10)
830 | when "e"
831 | cr.move_to(@x+10,@y)
832 | cr.line_to(t.x-10,t.y)
833 | when "w"
834 | cr.move_to(@x-10,@y)
835 | cr.line_to(t.x+10,t.y)
836 | end
837 |
838 | case @path_mode
839 | when "horz"
840 | case rel_pos
841 | when "ne"
842 | cr.move_to(@x+10,@y)
843 | cr.line_to(t.x,@y)
844 | cr.line_to(t.x,t.y+10)
845 | when "nw"
846 | cr.move_to(@x-10,@y)
847 | cr.line_to(t.x,@y)
848 | cr.line_to(t.x,t.y+10)
849 | when "se"
850 | cr.move_to(@x+10,@y)
851 | cr.line_to(t.x,@y)
852 | cr.line_to(t.x,t.y-10)
853 | when "sw"
854 | cr.move_to(@x-10,@y)
855 | cr.line_to(t.x,@y)
856 | cr.line_to(t.x,t.y-10)
857 | end
858 | when "vert"
859 | case rel_pos
860 | when "ne"
861 | cr.move_to(@x,@y-10)
862 | cr.line_to(@x,t.y)
863 | cr.line_to(t.x-10,t.y)
864 | when "nw"
865 | cr.move_to(@x,@y-10)
866 | cr.line_to(@x,t.y)
867 | cr.line_to(t.x+10,t.y)
868 | when "se"
869 | cr.move_to(@x,@y+10)
870 | cr.line_to(@x,t.y)
871 | cr.line_to(t.x-10,t.y)
872 | when "sw"
873 | cr.move_to(@x,@y+10)
874 | cr.line_to(@x,t.y)
875 | cr.line_to(t.x+10,t.y)
876 | end
877 | when "line"
878 | cr.move_to(@x,@y)
879 | cr.line_to(t.x,t.y)
880 | end
881 | cr.set_line_cap(1) #Round
882 | cr.set_line_join(2) #Miter
883 | cr.set_line_width(3)
884 | cr.stroke
885 | end
886 |
887 | def trace_path_from(cr,s)
888 | case s.path_mode
889 | when "horz"
890 | cr.move_to(s.x,s.y)
891 | cr.line_to(@x,s.y)
892 | cr.line_to(@x,@y)
893 | when "vert"
894 | cr.move_to(s.x,s.y)
895 | cr.line_to(s.x,@y)
896 | cr.line_to(@x,@y)
897 | when "line"
898 | cr.move_to(s.x,s.y)
899 | cr.line_to(@x,@y)
900 | end
901 | end
902 |
903 | def input_mark_draw(cr,rel_pos,s)
904 | if s.path_mode == "horz"
905 | case rel_pos
906 | when "n","ne","nw"
907 | draw_chevron(cr,@chev_offsets[0],"n",self)
908 | @chev_offsets[0] += 5
909 | when "s","se","sw"
910 | draw_chevron(cr,@chev_offsets[1],"s",self)
911 | @chev_offsets[1] += 5
912 | when "e"
913 | draw_chevron(cr,@chev_offsets[2],"e",self)
914 | @chev_offsets[2] += 5
915 | when "w"
916 | draw_chevron(cr,@chev_offsets[3],"w",self)
917 | @chev_offsets[3] += 5
918 | end
919 | elsif s.path_mode == "vert"
920 | case rel_pos
921 | when "n"
922 | draw_chevron(cr,@chev_offsets[0],"n",self)
923 | @chev_offsets[0] += 5
924 | when "s"
925 | draw_chevron(cr,@chev_offsets[1],"s",self)
926 | @chev_offsets[1] += 5
927 | when "e","ne","se"
928 | draw_chevron(cr,@chev_offsets[2],"e",self)
929 | @chev_offsets[2] += 5
930 | when "w","nw","sw"
931 | draw_chevron(cr,@chev_offsets[3],"w",self)
932 | @chev_offsets[3] += 5
933 | end
934 | end
935 | end
936 |
937 | end
938 |
939 | class Traveler #A traveler handles the source note playing and creates another traveler if the destination is reached.
940 | attr_reader :remove, :dest, :dest_origin, :last_played_note, :played_note
941 | attr_accessor :reached
942 | 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.
943 | @srce = srce
944 | @dest = dest
945 | @srce_origin = srce.origin
946 | @dest_origin = dest.origin
947 | @repeat = dest.repeat
948 | @travel_c = 0
949 | @iteration = 0
950 | @last_played_note = lpn
951 | @played_note = nil
952 | @distance = ((@dest_origin[0] - @srce_origin[0]).abs + (@dest_origin[1] - @srce_origin[1]).abs)/CC.grid_spacing
953 | @reached = false
954 | @remove = false
955 | end
956 |
957 | def travel
958 | @travel_c += 1
959 | if @travel_c == @distance
960 | @dest.playing = true
961 | @reached = true
962 | play_check(@srce.use_rel,@dest.use_rel)
963 | elsif @travel_c == (@distance + @dest.duration)
964 | @dest.playing = false
965 | CC.repeaters << Repeater.new(@dest,@repeat,@played_note) if @repeat > 0
966 | queue_removal
967 | end
968 | end
969 |
970 | def play_check(srce_rel,dest_rel)
971 | if !dest_rel
972 | @played_note = @dest.note
973 | play_note
974 | elsif srce_rel && dest_rel
975 | @played_note = @last_played_note
976 | play_relative
977 | elsif !srce_rel && dest_rel
978 | @played_note = @srce.note.to_i
979 | play_relative
980 | end
981 | end
982 | def play_note
983 | queued_notes = []
984 | CC.queued_note_plays.each {|q| queued_notes << [q.note,q.chan]}
985 | CC.queued_note_plays << NoteSender.new(@played_note,@dest.channel,@dest.velocity,@dest.mute) unless queued_notes.find {|f| @played_note == f[0] && @dest.channel == f[1]}
986 | end
987 | def play_relative
988 | #search the current scales variable and round to nearest note.
989 | rel = @dest.note.to_i
990 | pn = @played_note
991 | if @dest.note.to_s.include?("+")
992 | (pn..127).each do |s|
993 | rel -= 1 if CC.scale_posns[s] == true && s != pn
994 | if rel == 0
995 | pn = s
996 | break
997 | end
998 | end
999 | elsif @dest.note.to_s.include?("-")
1000 | (0..pn).reverse_each do |s|
1001 | rel += 1 if CC.scale_posns[s] == true && s != pn
1002 | if rel == 0
1003 | pn = s
1004 | break
1005 | end
1006 | end
1007 | end
1008 | @played_note = pn
1009 | play_note
1010 | end
1011 |
1012 | def queue_removal
1013 | CC.queued_note_stops << NoteSender.new(@played_note,@dest.channel,0,@dest.mute)
1014 |
1015 | @remove = true
1016 | end
1017 |
1018 | end
1019 |
1020 | class Starter #A starter handles notes that are used as starting points for paths and point jumps for portals
1021 | attr_reader :remove, :last_played_note
1022 | def initialize(portal_srce,srce,lpn)
1023 | @travel_c = 0
1024 | @srce = srce
1025 | @portal_srce = portal_srce
1026 | @duration = srce.duration
1027 | @remove = false
1028 | @repeat = srce.repeat
1029 | @played_note = nil
1030 | @last_played_note = lpn
1031 |
1032 | @srce.playing = true
1033 | if @portal_srce != nil
1034 | play_check(@portal_srce.use_rel,@srce.use_rel)
1035 | else
1036 | @played_note = @srce.note
1037 | play_note
1038 | end
1039 | end
1040 |
1041 | def travel
1042 | @travel_c += 1
1043 | if @travel_c == @duration
1044 | @srce.playing = false
1045 | CC.queued_note_stops << NoteSender.new(@played_note,@srce.channel,0,@srce.mute)
1046 | CC.repeaters << Repeater.new(@srce,@repeat,@played_note) if @repeat > 0
1047 | @remove = true
1048 | end
1049 | end
1050 |
1051 | def play_check(portal_srce_rel,srce_rel)
1052 | if !srce_rel
1053 | @played_note = @srce.note
1054 | play_note
1055 | elsif portal_srce_rel && srce_rel
1056 | @played_note = @last_played_note
1057 | play_relative
1058 | elsif !portal_srce_rel && srce_rel
1059 | @played_note = @portal_srce.note.to_i
1060 | play_relative
1061 | end
1062 | end
1063 | def play_note
1064 | queued_notes = []
1065 | CC.queued_note_plays.each {|q| queued_notes << [q.note,q.chan]}
1066 | CC.queued_note_plays << NoteSender.new(@played_note,@srce.channel,@srce.velocity,@srce.mute) unless queued_notes.find {|f| @played_note == f[0] && @srce.channel == f[1]}
1067 | end
1068 | def play_relative
1069 | #search the current scales variable and round to nearest note.
1070 | rel = @srce.note.to_i
1071 | pn = @played_note
1072 | if @srce.note.to_s.include?("+")
1073 | (pn..127).each do |s|
1074 | rel -= 1 if CC.scale_posns[s] == true && s != pn
1075 | if rel == 0
1076 | pn = s
1077 | break
1078 | end
1079 | end
1080 | elsif @srce.note.to_s.include?("-")
1081 | (0..pn).reverse_each do |s|
1082 | rel += 1 if CC.scale_posns[s] == true && s != pn
1083 | if rel == 0
1084 | pn = s
1085 | break
1086 | end
1087 | end
1088 | end
1089 | @played_note = pn
1090 | play_note
1091 | end
1092 |
1093 | end
1094 |
1095 | class Repeater #A repeater handles notes that are set to repeat/arpeggiate. This logic is currently FUCKED
1096 | attr_reader :remove
1097 | def initialize(srce,count,played_note)
1098 | @srce = srce
1099 | @dur = srce.duration
1100 | @timer = (count * @dur)
1101 | @played_note = played_note
1102 | end
1103 |
1104 | def repeat
1105 | if @timer == 0
1106 | CC.queued_note_stops << NoteSender.new(@played_note,@srce.channel,0,@srce.mute)
1107 | @srce.repeating = false
1108 | @remove = true
1109 | end
1110 | unless @remove == true
1111 | @srce.repeating = true
1112 | play_note if @timer % @dur == 0
1113 | end
1114 | @timer -= 1
1115 | end
1116 |
1117 | def play_note
1118 | queued_notes = []
1119 | CC.queued_note_plays.each {|q| queued_notes << [q.note,q.chan]}
1120 | CC.queued_note_plays << NoteSender.new(@played_note,@srce.channel,@srce.velocity,@srce.mute) unless queued_notes.find {|f| @played_note == f[0] && @srce.channel == f[1]}
1121 | end
1122 |
1123 | end
1124 |
1125 | Pl = Point_Logic.new
--------------------------------------------------------------------------------
/COPYING.L:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | Midinous combines generative features with a grid-based GUI to offer a supplemental unique compositional experience
635 | Copyright (C) 2019 James "Nornec" Ratliff
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Midinous Copyright (C) 2019 James "Nornec" Ratliff
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/lib/midinous/style/midinous.glade:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
21 |
53 |
54 | Note Octave
55 | -2 -1 0 1 2 3 4 5 6 7 8
56 | C 0 12 24 36 48 60 72 84 96 108 120
57 | C# 1 13 25 37 49 61 73 85 97 109 121
58 | D 2 14 26 38 50 62 74 86 98 110 122
59 | D# 3 15 27 39 51 63 75 87 99 111 123
60 | E 4 16 28 40 52 64 76 88 100 112 124
61 | F 5 17 29 41 53 65 77 89 101 113 125
62 | F# 6 18 30 42 54 66 78 90 102 114 126
63 | G 7 19 31 43 55 67 79 91 103 115 127
64 | G# 8 20 32 44 56 68 80 92 104 116 -
65 | A 9 21 33 45 57 69 81 93 105 117 -
66 | A# 10 22 34 46 58 70 82 94 106 118 -
67 | B 11 23 35 47 59 71 83 95 107 119 -
68 |
69 |
70 | False
71 | Notes by Integer
72 | accessories-calculator-symbolic
73 | False
74 |
75 |
76 |
77 |
78 |
79 | True
80 | False
81 |
82 |
83 | 219
84 | 215
85 | 10
86 | 10
87 | 10
88 | 10
89 | True
90 | True
91 | False
92 | False
93 | help_text_1
94 | False
95 | True
96 |
97 |
98 |
99 |
100 |
101 |
102 | W = whole tone, H = Half tone
103 | Scales and their relative intervals
104 | Aeolian R-W-H-W-W-H-W-W
105 | Altered R-H-W-H-W-W-W-W
106 | Augmented R-3H-H-3H-H-3H-H
107 | Blues R-3H-W-H-H-3H-W
108 | Chromatic R-H-H-H-H-H-H-H-H-H-H-H-H
109 | Dorian R-W-H-W-W-W-H-W
110 | Flamenco R-H-3H-H-W-H-3H-H
111 | Half Diminished R-W-H-W-H-W-W-W
112 | Harmonic major R-W-W-H-W-H-3H-H
113 | Harmonic minor R-W-H-W-W-H-3H-H
114 | Hirajoshi R-2W-W-H-2W-H
115 | Hungarian R-W-H-3H-H-H-3H-H
116 | Insen R-H-2W-W-2W-W
117 | Ionian R-W-W-H-W-W-W-H
118 | Iwato R-H-2W-H-2W-W
119 | Locrian R-H-W-W-H-W-W-W
120 | Locrian Major R-W-W-H-H-W-W-W
121 | Lydian R-W-W-W-H-W-W-H
122 | Lydian Augmented R-W-W-W-W-H-W-H
123 | Pentatonic Major R-W-W-3H-W-3H
124 | Pentatonic Minor R-3H-W-W-3H-W
125 | Melodic minor R-W-H-W-W-W-W-H
126 | Mixolydian R-W-W-H-W-W-H-W
127 | Octatonic Whole R-W-H-W-H-W-H-W-H
128 | Octatonic Half R-H-W-H-W-H-W-H-W
129 | Persian R-H-3H-H-H-W-3H-H
130 | Phrygian R-H-W-W-W-H-W-W
131 | Prometheus R-W-W-W-3H-H-W
132 | Tritone R-H-3H-W-H-3H-W
133 | Two-semitone Tritone R-H-H-4H-H-H
134 | Ukrainian Dorian R-W-H-3H-H-W-H-W
135 | Whole Tone R-W-W-W-W-W-W
136 |
137 |
138 | False
139 | Scales and Intervals
140 | accessories-calculator-symbolic
141 | False
142 |
143 |
144 |
145 |
146 |
147 | True
148 | False
149 |
150 |
151 | 267
152 | 510
153 | 10
154 | 10
155 | 10
156 | 10
157 | True
158 | True
159 | False
160 | False
161 | help_text_2
162 | False
163 | True
164 |
165 |
166 |
167 |
168 |
169 |
170 | [ -- decrement grid size/change time signature divisor -1
171 | ] -- increment grid size/ change time signature divisor +1
172 | { -- decrement time signature denominator (slows down play) - min 2
173 | } -- increment time signature denominator (speeds up play) - max 16
174 | delete -- deletes selected point(s)
175 | home -- deletes paths entering selected point(s)
176 | end -- deletes paths leaving selected point(s)
177 | Page Up -- change path direction
178 | Page Down -- change path direction
179 | A -- Make starting point (need at least 1 to begin play)
180 | T -- Built path - build a path between one or more points
181 | Q -- Select Tool - Box select or single select points
182 | W -- Point Place Tool - Places a point
183 | E -- Move Tool - moves selected points based on a stencil
184 | R -- Path Tool - Select source point, select target points, push build path or hit 'T'
185 | M -- Toggle mute on selected points
186 | < and > -- Cycle Play Mode - between robin, portal (random, split, if more than one path is leaving a point)
187 | , and . -- Cycle starting path if more than one path is leaving a point
188 | + and - -- add or subtract 1 from the currently selected points' note
189 | Num 6 -- play
190 | Num 5 -- stop
191 | space -- toggle play/stop
192 | Ctrl+A -- select all
193 | Ctrl+C -- copy selected points
194 | Ctrl+X -- cut selected points
195 | Ctrl+V -- paste copied/cut points
196 | Ctrl+O -- Open
197 | Ctrl+S -- Save
198 | Shift+Ctrl+S -- Save As
199 | Ctrl+N -- New
200 | Ctrl+Q -- Quit
201 |
202 |
203 | False
204 | Hotkeys
205 | accessories-calculator-symbolic
206 | False
207 |
208 |
209 |
210 |
211 |
212 | True
213 | False
214 |
215 |
216 | 100
217 | 80
218 | 10
219 | 10
220 | 10
221 | 10
222 | True
223 | True
224 | False
225 | False
226 | help_text_3
227 | False
228 | True
229 |
230 |
231 |
232 |
233 |
234 |
235 | Confirmation
236 | accessories-calculator-symbolic
237 | True
238 | true
239 | False
240 | dialog
241 | False
242 | True
243 |
244 |
245 |
246 |
247 |
248 | False
249 | vertical
250 | 2
251 |
252 |
253 | False
254 | end
255 |
256 |
257 | Confirm
258 | True
259 | True
260 | False
261 |
262 |
263 | True
264 | True
265 | 0
266 |
267 |
268 |
269 |
270 | Cancel
271 | True
272 | True
273 | True
274 |
275 |
276 | True
277 | True
278 | 1
279 |
280 |
281 |
282 |
283 | False
284 | False
285 | 0
286 |
287 |
288 |
289 |
290 | True
291 | False
292 |
293 |
294 | 376
295 | 32
296 | True
297 | False
298 | There are unsaved changes. Do you wish to proceed?
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 | False
307 | True
308 | 1
309 |
310 |
311 |
312 |
313 |
314 |
315 | File Operation
316 | accessories-calculator-symbolic
317 | False
318 | dialog
319 | False
320 | False
321 | False
322 | False
323 | True
324 | True
325 |
326 |
327 |
328 |
329 |
330 | False
331 | vertical
332 | 2
333 |
334 |
335 | False
336 | end
337 |
338 |
339 | Placeholder
340 | True
341 | True
342 | False
343 |
344 |
345 | True
346 | True
347 | 0
348 |
349 |
350 |
351 |
352 | Cancel
353 | True
354 | True
355 | True
356 |
357 |
358 | True
359 | True
360 | 1
361 |
362 |
363 |
364 |
365 | False
366 | False
367 | 0
368 |
369 |
370 |
371 |
372 | True
373 | False
374 |
375 |
376 | 299
377 | 20
378 | True
379 | True
380 |
381 |
382 | 155
383 |
384 |
385 |
386 |
387 | 83
388 | 34
389 | True
390 | False
391 | Filename:
392 |
393 |
394 |
395 |
396 |
397 | 72
398 |
399 |
400 |
401 |
402 | False
403 | True
404 | 2
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 | 3000
415 | 1
416 | 10
417 |
418 |
419 | 3000
420 | 1
421 | 10
422 |
423 |
424 | 300
425 | 1
426 | 10
427 |
428 |
429 | 1200
430 | 1
431 | 10
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 | 1
449 | 999
450 | 120
451 | 1
452 | 10
453 |
454 |
455 | 0
456 | 127
457 | 60
458 | 1
459 | 5
460 |
461 |
462 | True
463 | False
464 | gtk-zoom-fit
465 |
466 |
467 | True
468 | False
469 | gtk-disconnect
470 |
471 |
472 | True
473 | False
474 | gtk-sort-descending
475 |
476 |
477 | True
478 | False
479 | gtk-convert
480 |
481 |
482 | Program Running Normally
483 |
484 |
485 |
486 |
487 | True
488 | midinous_main
489 | False
490 | false
491 | True
492 | Midinous
493 | accessories-calculator-symbolic
494 |
495 |
496 |
497 |
498 |
499 | True
500 | False
501 |
502 |
503 | 1761
504 | 32
505 | True
506 | False
507 |
508 |
509 | True
510 | False
511 | _File
512 | True
513 |
514 |
515 | True
516 | False
517 |
518 |
519 | gtk-new
520 | True
521 | False
522 | True
523 | True
524 |
525 |
526 |
527 |
528 | gtk-open
529 | True
530 | False
531 | True
532 | True
533 |
534 |
535 |
536 |
537 | gtk-save
538 | True
539 | False
540 | True
541 | True
542 |
543 |
544 |
545 |
546 | gtk-save-as
547 | True
548 | False
549 | True
550 | True
551 |
552 |
553 |
554 |
555 | True
556 | False
557 |
558 |
559 |
560 |
561 | gtk-quit
562 | True
563 | False
564 | True
565 | True
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 | True
575 | False
576 | _Edit
577 | True
578 |
579 |
580 | True
581 | False
582 |
583 |
584 | _IO Regenerate
585 | True
586 | False
587 | True
588 | True
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 | True
598 | False
599 | _Input
600 | True
601 |
602 |
603 | True
604 | False
605 |
614 |
615 |
616 |
617 |
618 |
619 |
620 | True
621 | False
622 | Input _Channel
623 | True
624 |
625 |
626 | True
627 | False
628 |
637 |
638 |
639 |
640 |
641 |
642 |
643 | True
644 | False
645 | _Output
646 | True
647 |
648 |
649 | True
650 | False
651 |
660 |
661 |
662 |
663 |
664 |
665 |
666 | True
667 | False
668 | _Help
669 | True
670 |
671 |
672 | True
673 | False
674 |
675 |
676 | _About
677 | True
678 | False
679 | True
680 | True
681 |
682 |
683 |
684 |
685 | _Notes by Integer
686 | True
687 | False
688 | True
689 | True
690 |
691 |
692 |
693 |
694 | _Scales and Intervals
695 | True
696 | False
697 | True
698 | True
699 |
700 |
701 |
702 |
703 | _Hotkeys
704 | True
705 | False
706 | True
707 | True
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 | 1360
719 | 500
720 | True
721 | False
722 |
723 |
724 | 183
725 | 25
726 | True
727 | True
728 | start
729 |
730 |
731 | True
732 | False
733 | False
734 | main_tool_img_1
735 | True
736 | False
737 |
738 |
739 | True
740 | True
741 | 0
742 |
743 |
744 |
745 |
746 | True
747 | False
748 | False
749 | main_tool_img_2
750 | True
751 | False
752 | main_tool_1
753 |
754 |
755 | True
756 | True
757 | 1
758 |
759 |
760 |
761 |
762 | True
763 | False
764 | False
765 | main_tool_img_3
766 | True
767 | False
768 | main_tool_1
769 |
770 |
771 | True
772 | True
773 | 2
774 |
775 |
776 |
777 |
778 | True
779 | False
780 | False
781 | main_tool_img_4
782 | True
783 | False
784 | main_tool_1
785 |
786 |
787 | True
788 | True
789 | 3
790 |
791 |
792 |
793 |
794 | 96
795 | 0
796 |
797 |
798 |
799 |
800 | Build Path
801 | 180
802 | 20
803 | True
804 | False
805 | True
806 |
807 |
808 | 633
809 |
810 |
811 |
812 |
813 | 155
814 | 28
815 | True
816 | False
817 | True
818 | start
819 |
820 |
821 | Stop
822 | True
823 | False
824 | True
825 |
826 |
827 | True
828 | True
829 | 0
830 |
831 |
832 |
833 |
834 | Play
835 | True
836 | False
837 | True
838 |
839 |
840 | True
841 | True
842 | 1
843 |
844 |
845 |
846 |
847 | 965
848 |
849 |
850 |
851 |
852 | 100
853 | 25
854 | True
855 | False
856 | Performance
857 |
858 |
859 |
860 |
861 |
862 | 855
863 | 4
864 |
865 |
866 |
867 |
868 | 75
869 | 35
870 | True
871 | False
872 | 11
873 | False
874 | 6
875 | False
876 | tool_descrip
877 | False
878 | True
879 |
880 |
881 | 438
882 | 0
883 |
884 |
885 |
886 |
887 | 259
888 | 500
889 | True
890 | False
891 |
892 |
893 | 130
894 | 30
895 | True
896 | False
897 | Point Properties
898 |
899 |
900 |
901 |
902 |
903 | 5
904 | 4
905 |
906 |
907 |
908 |
909 | 168
910 | 34
911 | True
912 | True
913 |
914 |
915 | 5
916 | 546
917 |
918 |
919 |
920 |
921 | 241
922 | 471
923 | True
924 | False
925 | in
926 |
927 |
928 | True
929 | False
930 |
931 |
932 | True
933 | False
934 | prop_list_model
935 |
936 |
937 |
938 |
939 |
940 | True
941 | 100
942 | Property
943 | True
944 | False
945 | -1
946 |
947 |
948 | 16
949 |
950 |
951 | 0
952 |
953 |
954 |
955 |
956 |
957 |
958 | True
959 | Value
960 | False
961 | -1
962 |
963 |
964 | 16
965 |
966 |
967 | 1
968 |
969 |
970 |
971 |
972 |
973 |
974 |
975 |
976 |
977 |
978 | 5
979 | 37
980 |
981 |
982 |
983 |
984 | 130
985 | 30
986 | True
987 | False
988 | Modify Property
989 |
990 |
991 |
992 |
993 |
994 | 5
995 | 513
996 |
997 |
998 |
999 |
1000 | Apply
1001 | 65
1002 | 67
1003 | True
1004 | False
1005 | True
1006 |
1007 |
1008 | 181
1009 | 515
1010 |
1011 |
1012 |
1013 |
1014 | 63
1015 | 32
1016 | True
1017 | False
1018 | Tempo
1019 |
1020 |
1021 |
1022 |
1023 |
1024 | 1
1025 | 586
1026 |
1027 |
1028 |
1029 |
1030 | 180
1031 | 34
1032 | True
1033 | True
1034 | tempo_adj
1035 | 5
1036 | 3
1037 | True
1038 | True
1039 | 120
1040 |
1041 |
1042 | 66
1043 | 585
1044 |
1045 |
1046 |
1047 |
1048 | 63
1049 | 32
1050 | True
1051 | False
1052 | Time Signature
1053 |
1054 |
1055 |
1056 |
1057 |
1058 | 5
1059 | 630
1060 |
1061 |
1062 |
1063 |
1064 | 50
1065 | 35
1066 | True
1067 | False
1068 | 8
1069 | False
1070 | 6
1071 | False
1072 | t_sig
1073 | False
1074 | True
1075 |
1076 |
1077 | 130
1078 | 630
1079 |
1080 |
1081 |
1082 |
1083 | 63
1084 | 32
1085 | True
1086 | False
1087 | Scale and Root
1088 |
1089 |
1090 |
1091 |
1092 |
1093 | 5
1094 | 670
1095 |
1096 |
1097 |
1098 |
1099 | 116
1100 | 34
1101 | True
1102 | True
1103 | root_adj
1104 | 5
1105 | True
1106 | True
1107 | 60
1108 |
1109 |
1110 | 130
1111 | 670
1112 |
1113 |
1114 |
1115 |
1116 | 242
1117 | 40
1118 | True
1119 | True
1120 | scale_tree_model
1121 |
1122 |
1123 | 16
1124 |
1125 |
1126 | 0
1127 |
1128 |
1129 |
1130 |
1131 | 4
1132 | 710
1133 |
1134 |
1135 |
1136 |
1137 | 1502
1138 |
1139 |
1140 |
1141 |
1142 | 33
1143 | 41
1144 | True
1145 | False
1146 |
1147 |
1148 | 815
1149 | -4
1150 |
1151 |
1152 |
1153 |
1154 | 1761
1155 | 22
1156 | 10
1157 | True
1158 | False
1159 | 4
1160 | False
1161 | False
1162 | status_area
1163 | False
1164 | True
1165 |
1166 |
1167 | 800
1168 |
1169 |
1170 |
1171 |
1172 | 99
1173 | 34
1174 | True
1175 | False
1176 | Toolbox
1177 |
1178 |
1179 |
1180 |
1181 |
1182 | 0
1183 |
1184 |
1185 |
1186 |
1187 | 36
1188 |
1189 |
1190 |
1191 |
1192 | 1500
1193 | 760
1194 | True
1195 | False
1196 | canvas_scroll_h
1197 | canvas_scroll_v
1198 | always
1199 | always
1200 | in
1201 | 1080
1202 | 760
1203 | 1080
1204 | 760
1205 |
1206 |
1207 | True
1208 | False
1209 | natural
1210 | natural
1211 |
1212 |
1213 | True
1214 | False
1215 | False
1216 | center
1217 | center
1218 |
1219 |
1220 |
1221 |
1222 |
1223 |
1224 | 73
1225 |
1226 |
1227 |
1228 |
1229 |
1230 |
1231 |
--------------------------------------------------------------------------------