├── 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 | 7 | Midinous combines generative features with a grid-based GUI to offer a supplemental unique compositional experience 8 | 9 | Copyright (C) 2019 James "Nornec" Ratliff 10 | 11 | Midinous is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | https://github.com/Nornec/Midinous 17 | https://nornec.bandcamp.com 18 | https://youtube.com/nornec 19 | https://rubygems.org/gems/midinous 20 | 21 | 22 | False 23 | About Midinous 24 | accessories-calculator-symbolic 25 | False 26 | 27 | 28 | 29 | 30 | 31 | True 32 | False 33 | 34 | 35 | 100 36 | 80 37 | 10 38 | 10 39 | 10 40 | 10 41 | True 42 | True 43 | False 44 | False 45 | about_text_1 46 | False 47 | True 48 | 49 | 50 | 51 | 52 | 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 | --------------------------------------------------------------------------------