├── .gitignore ├── CHANGELOG.txt ├── INSTALL.txt ├── NOTES.txt ├── README.md ├── Rakefile ├── ajm.ruby.properties.example ├── ajm ├── ajm.overview.maxpat ├── help │ ├── ajm.about.maxpat │ ├── ajm.busymap.maxhelp │ ├── ajm.cosy.maxhelp │ ├── ajm.env~.maxhelp │ ├── ajm.error.maxhelp │ ├── ajm.eval.maxhelp │ ├── ajm.help.template.maxpat │ ├── ajm.helplinks.maxpat │ ├── ajm.lfo~.maxhelp │ ├── ajm.makenote.maxhelp │ ├── ajm.metro.maxhelp │ ├── ajm.midi2coll.maxhelp │ ├── ajm.polyroute.maxhelp │ ├── ajm.psui.maxhelp │ ├── ajm.rseq.maxhelp │ ├── ajm.rseq2seq.maxhelp │ ├── ajm.ruby.advanced.maxhelp │ ├── ajm.ruby.maxhelp │ ├── ajm.seealso.maxpat │ ├── ajm.seq.maxhelp │ ├── ajm_cc_pan.mid │ ├── ajm_env_presets.xml │ ├── ajm_midi2coll.mid │ ├── ajm_multitrack.mid │ ├── ajm_shifted_scale.mid │ ├── ajm_timesig.mid │ └── compusition.gif ├── js │ ├── ajm.error.js │ ├── ajm.polyroute-helper.js │ └── ajm.psui.js ├── misc │ ├── ajm.bring-help-to-front-button.maxpat │ ├── ajm.bring-to-front-listener.maxpat │ ├── ajm.bring-to-front.maxpat │ ├── ajm.env-polysaw.maxpat │ ├── ajm.envthispoly.maxpat │ ├── ajm.envui-helper.maxpat │ ├── ajm.help-transport.maxpat │ ├── ajm.next-quantized-timepoint.maxpat │ ├── ajm.polymakenote.maxpat │ ├── ajm.polyprint.maxpat │ ├── ajm.translate-duration.maxpat │ └── ajm.translate-line.maxpat ├── objects │ ├── ajm.busymap.maxpat │ ├── ajm.cosy.maxpat │ ├── ajm.envui.maxpat │ ├── ajm.env~.maxpat │ ├── ajm.lfo~.maxpat │ ├── ajm.makenote.maxpat │ ├── ajm.metro.maxpat │ ├── ajm.midi2coll.maxpat │ ├── ajm.polyroute.maxpat │ └── ajm.rseq2seq.maxpat └── ruby │ ├── ajm_cosy.rb │ ├── ajm_cosy2coll.rb │ ├── ajm_midi2coll.rb │ ├── ajm_ruby_initialize.rb │ ├── ajmfl_cosy2clip.rb │ ├── help │ ├── ajm_argv.rb │ ├── ajm_autowatch.rb │ ├── ajm_call_send.rb │ ├── ajm_file_example.rb │ ├── ajm_inlet_symbols_to.rb │ ├── ajm_inlets.rb │ ├── ajm_load_require.rb │ ├── ajm_scriptfile.rb │ ├── ajm_variable_scope.rb │ └── ajm_version.rb │ ├── midilib │ ├── consts.rb │ ├── event.rb │ ├── info.rb │ ├── io │ │ ├── midifile.rb │ │ ├── seqreader.rb │ │ └── seqwriter.rb │ ├── measure.rb │ ├── sequence.rb │ ├── track.rb │ └── utils.rb │ └── webserver │ ├── ajm_servlets.rb │ ├── ajm_webserver.rb │ ├── ajm_webserver_example.rb │ ├── favicon.ico │ ├── filebrowse │ ├── file.html │ └── file.txt │ ├── index.html │ └── web │ ├── erb.rhtml │ └── to_max.html ├── build.xml ├── lib ├── ant-junit.jar ├── bsf.jar ├── jruby.jar ├── junit-4.5.jar └── max.jar ├── license ├── LICENSE-ajm-objects.txt ├── LICENSE-bsf.txt ├── LICENSE-midilib.txt └── jruby │ ├── COPYING │ ├── COPYING.CPL │ ├── COPYING.GPL │ └── COPYING.LGPL └── src └── ajm ├── code.java ├── eval.java ├── maxsupport ├── AbstractMaxObject.java ├── AbstractMaxRubyObject.java └── Atomizer.java ├── preemptrseq.java ├── print.java ├── rseq.java ├── rseqTest.java ├── ruby.java ├── rubysupport ├── AbstractScriptEvaluator.java ├── BSFRubyEvaluator.java ├── IdInUseException.java ├── JRubyEmbedEvaluator.java ├── MaxRubyAdapter.java ├── RubyException.java ├── RubyProperties.java ├── ScriptEvaluator.java ├── ScriptEvaluatorManager.java └── SymbolConversionOption.java ├── seq.java ├── seqTest.java ├── seqsupport ├── Item.java ├── Parser.java ├── ParserTest.java └── Token.java └── util ├── DummyMaxObject.java ├── FileWatcher.java ├── GlobalVariableStore.java ├── LineBuilder.java ├── Logger.java ├── MappedSet.java ├── TextBlock.java ├── TextViewer.java └── Utils.java /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | download 3 | dist 4 | ajm.jar 5 | -------------------------------------------------------------------------------- /INSTALL.txt: -------------------------------------------------------------------------------- 1 | 2 | ajm objects for Max/MSP 3 | 4 | Version @@VERSION 5 | 6 | Built @@BUILD_DATE 7 | 8 | http://compusition.com/web/software/maxmsp/ajm-objects 9 | 10 | Adam Murray (adam@compusition.com) 11 | 12 | ------------------------------------------------------------------------------ 13 | 14 | REQUIREMENTS 15 | 16 | * Max/MSP 5 17 | 18 | * Java 5 or higher (most computers today have this) 19 | 20 | ----------------------------------------------------------------------------- 21 | 22 | FIRST TIME INSTALLATION 23 | 24 | (1) Put lib/ajm.jar and lib/jruby.jar in: 25 | Max5/Cycling '74/java/lib 26 | 27 | (2) Put the ajm folder somewhere on your Max file search path. 28 | See "Options -> File Preferences" under Max's Options menu. 29 | 30 | (3) Restart Max/MSP and open ajm/ajm.overview.maxpat 31 | 32 | ----------------------------------------------------------------------------- 33 | 34 | UPGRADING FROM VERSION 0.9 (or newer) 35 | 36 | (1) Delete the old ajm folder (the old patches & help files) 37 | 38 | (2) Proceed with the first time installation instructions. 39 | The following files will be replaced: ajm.jar, jruby.jar 40 | 41 | ----------------------------------------------------------------------------- 42 | 43 | UPGRADING FROM VERSION 0.8.8 (or older) 44 | 45 | (1) Delete the entire ruby directory structure: 46 | Max5/Cycling '74/java/lib/ruby 47 | 48 | (2) Delete the old ajm folder (the old patches & help files) 49 | 50 | (3) Delete the file: 51 | Max5/Cycling '74/java/lib/ajm.ruby.properties 52 | 53 | (4) Delete the file: 54 | Max5/Cycling '74/java/lib/bsf.jar 55 | 56 | (5) Proceed with the first time installation instructions. 57 | The following files will be replaced: ajm.jar, jruby.jar 58 | -------------------------------------------------------------------------------- /NOTES.txt: -------------------------------------------------------------------------------- 1 | 2 | ajm objects for Max/MSP 3 | 4 | Version @@VERSION 5 | 6 | http://compusition.com/web/software/maxmsp/ajm-objects 7 | 8 | Adam Murray (adam@compusition.com) 9 | 10 | 11 | See INSTALL.txt for installation instructions 12 | ------------------------------------------------------------------------------ 13 | 14 | NOTES 15 | 16 | Sequencing with good timing 17 | When using a [metro] or [ajm.metro] to sequence music, you should enable 18 | Overdrive from Max's Options menu. If Overdrive is not enabled, interacting 19 | with your Max patch (clicking a GUI object) can cause timing delays and ruin 20 | the sense of rhythm. This is normal Max scheduler behavior, so this advice 21 | applies whenever you need good timing from [metro]. 22 | 23 | ------------------------------------------------------------------------------ 24 | 25 | SOURCE CODE 26 | 27 | ajm objects is an open source project. The source code is located at: 28 | http://github.com/adamjmurray/ajm_objects 29 | 30 | Feel free to hack away at it, contribute features, or whatever you want. 31 | Just mind my copyright and give credit where credit is due. 32 | 33 | ------------------------------------------------------------------------------ 34 | 35 | DEPENDENCIES 36 | 37 | JRuby (http://jruby.org/) 38 | The 100% Java implementation of Ruby. 39 | JRuby in turn depends on other libraries. I included the license info 40 | for bsf.jar under the license folder. You can find license info for 41 | the Ruby language at: 42 | http://www.ruby-lang.org/en/LICENSE.txt 43 | http://www.ruby-lang.org/en/COPYING.txt 44 | 45 | midilib (http://midilib.rubyforge.org/) 46 | A Ruby MIDI library for reading and writing MIDI files. 47 | Used by ajm.midi2coll 48 | 49 | ------------------------------------------------------------------------------ 50 | 51 | Copyright (c) 2008-2011, Adam Murray (adam@compusition.com). 52 | 53 | All rights reserved. 54 | 55 | Redistribution and use of ajm objects in source and binary forms, with or 56 | without modification, are permitted provided that the following conditions 57 | are met: 58 | 59 | 1. Redistributions of source code must retain the above copyright 60 | notice, this list of conditions and the following disclaimer. 61 | 62 | 2. Redistributions in binary form must reproduce the above copyright 63 | notice, this list of conditions and the following disclaimer in 64 | the documentation and/or other materials provided with the 65 | distribution. 66 | 67 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 68 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 69 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 70 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 71 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 72 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 73 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 74 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 75 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 76 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 77 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ajm objects # 2 | 3 | A library of objects for Max 5 () 4 | 5 | 6 | 7 | 8 | ## Author ## 9 | 10 | Adam Murray (adam@compusition.com) 11 | 12 | 13 | ## Status ## 14 | 15 | Version 0.9.2 is the latest stable version, released on November 26, 2009. 16 | 17 |
18 | 19 | Tested on OS X Leopard & Snow Leopard, Windows XP, and with Max for Live. 20 | 21 | 22 | ## Future ## 23 | 24 | * ajm.ruby is being split off into a separate project at so I can focus on development of that object, and not require installation of all these objects for people who are only interested in scripting Max with Ruby. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | require 'tempfile' 3 | include FileUtils 4 | 5 | VERSION = '0.9.2' 6 | BUILD_DATE = Time.now.utc.strftime '%B %d, %Y (%H:%M GMT)' 7 | MANIFEST = 8 | "Library: ajm objects (MXJ) for MaxMSP 9 | Version: #{VERSION} 10 | Built-Date: #{BUILD_DATE} 11 | Author: Adam Murray 12 | URL: http://compusition.com 13 | " 14 | 15 | SRC = 'src' 16 | LIB = 'lib' 17 | PATCHES = 'ajm' 18 | LICENSE = 'license' 19 | BUILD = 'build' 20 | DIST = 'dist' 21 | PACKAGE = "#{DIST}/ajm-objects-#{VERSION}" 22 | 23 | SOURCES = FileList["#{SRC}/**/*.java"].exclude(/Test\.java$/) 24 | CLASSPATH = FileList["#{LIB}/**/*.jar"].exclude(/^ajm.jar$/) 25 | JAR = "#{LIB}/ajm.jar" 26 | 27 | 28 | ############################################################################## 29 | # TASK DEFINITIONS 30 | 31 | CLEAN.include BUILD, JAR 32 | CLOBBER.include DIST 33 | 34 | 35 | desc 'compile the java source files' 36 | task :compile do 37 | mkdir BUILD 38 | puts "Compiling java sources to #{BUILD}/" 39 | `javac -classpath #{CLASSPATH.join ':'} -d #{BUILD} -g -source 1.5 -target 1.5 #{SOURCES}` 40 | end 41 | 42 | 43 | desc 'construct the jar archive of the compiled java sources' 44 | task :jar => [:clean, :compile] do 45 | manifest = Tempfile.new('manifest') 46 | File.open(manifest, 'w') {|io| io.write MANIFEST } 47 | puts "Constructing #{JAR}" 48 | `jar cvfm #{JAR} #{manifest.path} -C #{BUILD} .` 49 | end 50 | 51 | 52 | desc 'prepare the files for distribution' 53 | task :package => [:jar] do 54 | puts "Preparing distribution package" 55 | package_lib = "#{PACKAGE}/#{LIB}" 56 | mkdir DIST 57 | mkdir PACKAGE 58 | mkdir package_lib 59 | 60 | # Collect the files 61 | FileList['*.txt', '*.example'].each do |filename| 62 | cp filename, PACKAGE 63 | end 64 | FileList["#{LIB}/*.jar"].exclude('max.jar', /^.*junit.*jar$/, 'bsf.jar').each do |filename| 65 | cp filename, package_lib 66 | end 67 | cp_r PATCHES, PACKAGE 68 | cp_r LICENSE, PACKAGE 69 | end 70 | 71 | 72 | desc 'search and replace variable values in text files' 73 | task :replace_vars => [:package] do 74 | puts "Performing search and replace for the VERSION and BUILD_DATE variables" 75 | plaintext_filetypes = ['txt', 'maxpat', 'maxhelp'] 76 | files = FileList[ plaintext_filetypes.map{|type| "#{PACKAGE}/**/*.#{type}" } ] 77 | files.replace_all '@@VERSION', VERSION 78 | files.replace_all '@@BUILD_DATE', BUILD_DATE 79 | end 80 | 81 | 82 | desc 'construct the distribution archive' 83 | task :dist => [:replace_vars] do 84 | mkdir DIST 85 | archive = "#{DIST}/#{PACKAGE}.zip" 86 | puts "Constructing distribution archive #{archive}" 87 | `zip -l -r #{archive} #{PACKAGE}` 88 | # The -l option converts newlines to crlf, which should display correctly on both OS X and Windows. 89 | # Otherwise, since I write these txt files on OS X, newlines would disappear when viewed in Notepad on Windows. 90 | end 91 | 92 | 93 | ############################################################################## 94 | # SUPPORT CODE: 95 | 96 | # I find it annoying that I always have to check if a directory exists 97 | # before creating it, so I monkey patch mkdir() to handle it automatically: 98 | alias original_mkdir mkdir 99 | def mkdir(dir) 100 | original_mkdir dir if not File.exist? dir 101 | end 102 | 103 | 104 | class FileList 105 | # Replaces all occurrences of token with value 106 | def replace_all(token, value) 107 | self.each do |filename| 108 | next if File.directory? filename 109 | contents = IO.read filename 110 | if contents.include? token 111 | contents.gsub! token, value 112 | File.open(filename, 'w') do |io| 113 | io.write contents 114 | end 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /ajm.ruby.properties.example: -------------------------------------------------------------------------------- 1 | ######################## 2 | # Options for ajm.ruby # 3 | ######################## 4 | 5 | # To use: 6 | # - Rename this file to ajm.ruby.properties 7 | # - Put it on the Max file search path 8 | # - Restart Max (and restart again each time you change this file) 9 | 10 | 11 | ruby.loadpaths = 12 | # A semicolon-separated list of absolute paths to append to the default $LOAD_PATH. 13 | # You can use this to add gems or other projects installed elsewhere. 14 | # Default value is nothing. 15 | 16 | 17 | # jruby.home = /Users/adam/bin/jruby-1.5.2 18 | # If you want to install a lot of gems: instead of adding a loadpath for each one, 19 | # you can install them into a separate jruby installation and set jruby.home to 20 | # run ajm.ruby against that jruby instead of the built-in one. 21 | # This variable is not set by default. 22 | 23 | 24 | ruby.initializers = ajm_ruby_initialize.rb 25 | # A semicolon-separated list of files. 26 | # These files will be run in the order listed whenever a new ajm.ruby context is initialized. 27 | # Default value is ajm_ruby_initialize.rb 28 | -------------------------------------------------------------------------------- /ajm/help/ajm.help.template.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 591.0, 187.0, 795.0, 454.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 591.0, 187.0, 795.0, 454.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "newobj", 22 | "text" : "thispatcher", 23 | "fontname" : "Verdana", 24 | "hidden" : 1, 25 | "numinlets" : 1, 26 | "patching_rect" : [ 548.0, 266.0, 73.0, 20.0 ], 27 | "fontsize" : 11.1, 28 | "numoutlets" : 2, 29 | "id" : "obj-31", 30 | "outlettype" : [ "", "" ], 31 | "save" : [ "#N", "thispatcher", ";", "#Q", "end", ";" ] 32 | } 33 | 34 | } 35 | , { 36 | "box" : { 37 | "maxclass" : "newobj", 38 | "text" : "ajm.bring-to-front-listener ajm.____", 39 | "fontname" : "Verdana", 40 | "hidden" : 1, 41 | "numinlets" : 0, 42 | "patching_rect" : [ 548.0, 240.0, 216.0, 20.0 ], 43 | "fontsize" : 11.1, 44 | "numoutlets" : 1, 45 | "id" : "obj-17", 46 | "outlettype" : [ "front" ] 47 | } 48 | 49 | } 50 | , { 51 | "box" : { 52 | "maxclass" : "newobj", 53 | "text" : "p subpatch", 54 | "fontname" : "Verdana", 55 | "numinlets" : 0, 56 | "patching_rect" : [ 499.0, 113.0, 71.0, 20.0 ], 57 | "fontsize" : 11.0, 58 | "numoutlets" : 0, 59 | "id" : "obj-10", 60 | "color" : [ 0.156863, 0.8, 0.54902, 1.0 ], 61 | "patcher" : { 62 | "fileversion" : 1, 63 | "rect" : [ 289.0, 47.0, 724.0, 428.0 ], 64 | "bglocked" : 0, 65 | "defrect" : [ 289.0, 47.0, 724.0, 428.0 ], 66 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 67 | "openinpresentation" : 0, 68 | "default_fontsize" : 11.1, 69 | "default_fontface" : 0, 70 | "default_fontname" : "Verdana", 71 | "gridonopen" : 0, 72 | "gridsize" : [ 15.0, 15.0 ], 73 | "gridsnaponopen" : 0, 74 | "toolbarvisible" : 1, 75 | "boxanimatetime" : 200, 76 | "imprint" : 0, 77 | "metadata" : [ ], 78 | "boxes" : [ ], 79 | "lines" : [ ] 80 | } 81 | , 82 | "saved_object_attributes" : { 83 | "default_fontsize" : 11.1, 84 | "fontname" : "Verdana", 85 | "fontface" : 0, 86 | "fontsize" : 11.1, 87 | "default_fontface" : 0, 88 | "default_fontname" : "Verdana", 89 | "globalpatchername" : "" 90 | } 91 | 92 | } 93 | 94 | } 95 | , { 96 | "box" : { 97 | "maxclass" : "bpatcher", 98 | "lockeddragscroll" : 1, 99 | "numinlets" : 1, 100 | "args" : [ ], 101 | "patching_rect" : [ 628.0, 386.0, 145.0, 55.0 ], 102 | "numoutlets" : 0, 103 | "id" : "obj-60", 104 | "name" : "ajm.seealso.maxpat" 105 | } 106 | 107 | } 108 | , { 109 | "box" : { 110 | "maxclass" : "bpatcher", 111 | "lockeddragscroll" : 1, 112 | "numinlets" : 0, 113 | "args" : [ ], 114 | "patching_rect" : [ 639.0, 6.0, 138.0, 55.0 ], 115 | "numoutlets" : 0, 116 | "id" : "obj-48", 117 | "name" : "ajm.helplinks.maxpat" 118 | } 119 | 120 | } 121 | , { 122 | "box" : { 123 | "maxclass" : "comment", 124 | "text" : "description", 125 | "fontname" : "Verdana", 126 | "numinlets" : 1, 127 | "patching_rect" : [ 141.0, 16.0, 336.0, 21.0 ], 128 | "fontsize" : 12.0, 129 | "numoutlets" : 0, 130 | "id" : "obj-56" 131 | } 132 | 133 | } 134 | , { 135 | "box" : { 136 | "maxclass" : "comment", 137 | "text" : "ajm.___", 138 | "fontname" : "Arial", 139 | "numinlets" : 1, 140 | "patching_rect" : [ 29.0, 8.0, 77.0, 27.0 ], 141 | "fontsize" : 18.0, 142 | "numoutlets" : 0, 143 | "id" : "obj-57" 144 | } 145 | 146 | } 147 | , { 148 | "box" : { 149 | "maxclass" : "comment", 150 | "text" : "Type", 151 | "fontname" : "Verdana", 152 | "numinlets" : 1, 153 | "patching_rect" : [ 30.0, 33.0, 100.0, 17.0 ], 154 | "fontsize" : 9.0, 155 | "numoutlets" : 0, 156 | "id" : "obj-58" 157 | } 158 | 159 | } 160 | , { 161 | "box" : { 162 | "maxclass" : "panel", 163 | "grad1" : [ 0.823529, 1.0, 0.956863, 1.0 ], 164 | "grad2" : [ 0.219608, 0.564706, 0.462745, 1.0 ], 165 | "angle" : 270.0, 166 | "numinlets" : 1, 167 | "patching_rect" : [ 27.0, 4.0, 464.0, 58.0 ], 168 | "mode" : 1, 169 | "rounded" : 12, 170 | "numoutlets" : 0, 171 | "id" : "obj-50" 172 | } 173 | 174 | } 175 | , { 176 | "box" : { 177 | "maxclass" : "panel", 178 | "grad1" : [ 0.866667, 0.866667, 0.866667, 1.0 ], 179 | "grad2" : [ 0.0, 0.0, 0.0, 1.0 ], 180 | "angle" : 270.0, 181 | "numinlets" : 1, 182 | "patching_rect" : [ 26.0, 3.0, 467.0, 62.0 ], 183 | "mode" : 1, 184 | "rounded" : 16, 185 | "numoutlets" : 0, 186 | "id" : "obj-54" 187 | } 188 | 189 | } 190 | ], 191 | "lines" : [ { 192 | "patchline" : { 193 | "source" : [ "obj-17", 0 ], 194 | "destination" : [ "obj-31", 0 ], 195 | "hidden" : 1, 196 | "midpoints" : [ ] 197 | } 198 | 199 | } 200 | ] 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /ajm/help/ajm.helplinks.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 271.0, 323.0, 466.0, 308.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 271.0, 323.0, 466.0, 308.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 1, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "enablehscroll" : 1, 19 | "enablevscroll" : 1, 20 | "devicewidth" : 0.0, 21 | "boxes" : [ { 22 | "box" : { 23 | "maxclass" : "newobj", 24 | "text" : "ajm.bring-to-front ajm.about", 25 | "numinlets" : 1, 26 | "fontsize" : 11.1, 27 | "numoutlets" : 0, 28 | "patching_rect" : [ 143.0, 83.0, 173.0, 20.0 ], 29 | "id" : "obj-3", 30 | "fontname" : "Verdana" 31 | } 32 | 33 | } 34 | , { 35 | "box" : { 36 | "maxclass" : "newobj", 37 | "text" : "ajm.bring-to-front ajm.overview", 38 | "numinlets" : 1, 39 | "fontsize" : 11.1, 40 | "numoutlets" : 0, 41 | "patching_rect" : [ 13.0, 115.0, 192.0, 20.0 ], 42 | "id" : "obj-2", 43 | "fontname" : "Verdana" 44 | } 45 | 46 | } 47 | , { 48 | "box" : { 49 | "maxclass" : "textbutton", 50 | "bordercolor" : [ 0.0, 0.0, 0.0, 1.0 ], 51 | "border" : 1, 52 | "texton" : "Stop", 53 | "text" : "© 2010 Adam Murray", 54 | "numinlets" : 1, 55 | "fontsize" : 12.0, 56 | "bgovercolor" : [ 0.784314, 0.909804, 0.917647, 1.0 ], 57 | "presentation_rect" : [ 4.0, 26.0, 123.0, 17.0 ], 58 | "bgcolor" : [ 0.827451, 0.827451, 0.827451, 1.0 ], 59 | "numoutlets" : 3, 60 | "outlettype" : [ "", "", "int" ], 61 | "patching_rect" : [ 143.0, 57.0, 124.0, 18.0 ], 62 | "presentation" : 1, 63 | "id" : "obj-12", 64 | "fontname" : "Arial", 65 | "rounded" : 20.0 66 | } 67 | 68 | } 69 | , { 70 | "box" : { 71 | "maxclass" : "textbutton", 72 | "bordercolor" : [ 0.0, 0.0, 0.0, 1.0 ], 73 | "border" : 1, 74 | "texton" : "Stop", 75 | "text" : "ajm overview", 76 | "numinlets" : 1, 77 | "fontsize" : 12.0, 78 | "bgovercolor" : [ 0.784314, 0.909804, 0.917647, 1.0 ], 79 | "presentation_rect" : [ 27.0, 5.0, 79.0, 17.0 ], 80 | "bgcolor" : [ 0.827451, 0.827451, 0.827451, 1.0 ], 81 | "numoutlets" : 3, 82 | "outlettype" : [ "", "", "int" ], 83 | "patching_rect" : [ 13.0, 56.0, 80.0, 18.0 ], 84 | "presentation" : 1, 85 | "id" : "obj-10", 86 | "fontname" : "Arial", 87 | "rounded" : 20.0 88 | } 89 | 90 | } 91 | , { 92 | "box" : { 93 | "maxclass" : "fpic", 94 | "numinlets" : 1, 95 | "presentation_rect" : [ 2.0, 4.0, 19.0, 20.0 ], 96 | "numoutlets" : 0, 97 | "patching_rect" : [ 44.0, 18.0, 19.0, 20.0 ], 98 | "pic" : "compusition.gif", 99 | "presentation" : 1, 100 | "id" : "obj-1" 101 | } 102 | 103 | } 104 | , { 105 | "box" : { 106 | "maxclass" : "panel", 107 | "border" : 1, 108 | "numinlets" : 1, 109 | "shadow" : 3, 110 | "presentation_rect" : [ 0.0, 0.0, 133.0, 49.0 ], 111 | "bgcolor" : [ 1.0, 1.0, 1.0, 1.0 ], 112 | "numoutlets" : 0, 113 | "patching_rect" : [ 91.0, 9.0, 127.0, 16.0 ], 114 | "presentation" : 1, 115 | "id" : "obj-9", 116 | "rounded" : 12 117 | } 118 | 119 | } 120 | , { 121 | "box" : { 122 | "maxclass" : "panel", 123 | "mode" : 1, 124 | "numinlets" : 1, 125 | "grad1" : [ 0.482353, 0.482353, 0.482353, 1.0 ], 126 | "presentation_rect" : [ 0.0, 0.0, 135.0, 51.0 ], 127 | "numoutlets" : 0, 128 | "grad2" : [ 0.0, 0.0, 0.0, 1.0 ], 129 | "angle" : 270.0, 130 | "patching_rect" : [ 90.0, 30.0, 131.0, 15.0 ], 131 | "presentation" : 1, 132 | "id" : "obj-70", 133 | "rounded" : 16 134 | } 135 | 136 | } 137 | ], 138 | "lines" : [ { 139 | "patchline" : { 140 | "source" : [ "obj-10", 0 ], 141 | "destination" : [ "obj-2", 0 ], 142 | "hidden" : 0, 143 | "midpoints" : [ ] 144 | } 145 | 146 | } 147 | , { 148 | "patchline" : { 149 | "source" : [ "obj-12", 0 ], 150 | "destination" : [ "obj-3", 0 ], 151 | "hidden" : 0, 152 | "midpoints" : [ ] 153 | } 154 | 155 | } 156 | ] 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /ajm/help/ajm_cc_pan.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/ajm/help/ajm_cc_pan.mid -------------------------------------------------------------------------------- /ajm/help/ajm_midi2coll.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/ajm/help/ajm_midi2coll.mid -------------------------------------------------------------------------------- /ajm/help/ajm_multitrack.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/ajm/help/ajm_multitrack.mid -------------------------------------------------------------------------------- /ajm/help/ajm_shifted_scale.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/ajm/help/ajm_shifted_scale.mid -------------------------------------------------------------------------------- /ajm/help/ajm_timesig.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/ajm/help/ajm_timesig.mid -------------------------------------------------------------------------------- /ajm/help/compusition.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/ajm/help/compusition.gif -------------------------------------------------------------------------------- /ajm/js/ajm.error.js: -------------------------------------------------------------------------------- 1 | // I'm using this simple object as a workaround for the lack of a feature in Max: 2 | // There is currently no way to print an arbitrary error message to the Max window 3 | // using something like [print] for error messages. 4 | 5 | var prefix = null 6 | if(jsarguments.length > 1) prefix = jsarguments[1] + ": " 7 | 8 | function anything() { 9 | var msg = "" 10 | if(prefix) msg = prefix 11 | 12 | // anything receives a messagename (first atom) and arguments array (remaining atoms): 13 | msg += messagename + " " 14 | for(var i=0; i MAX_VOICES) { 55 | voices = MAX_VOICES; 56 | } 57 | 58 | var args = new Array(); 59 | for(var i=1; i<=voices; i++) { 60 | args.push("/"+i); 61 | } 62 | var voiceRoute = patcher.newobject("newex", 71, 170, 617, 196617, "OSC-route", args); 63 | 64 | patcher.connect(zlReg, 0, voiceRoute, 0); 65 | for(var i=1; i<=voices; i++) { 66 | var prepend = patcher.newobject("newex", i*66, 220, 61, 196617, "prepend", i); 67 | patcher.connect(voiceRoute, i-1, prepend, 0); 68 | patcher.connect(prepend, 0, zlSlice, 0); 69 | } 70 | // and pass anything that doesn't match to the outlet: 71 | patcher.connect(voiceRoute, voices, out, 0); 72 | } 73 | -------------------------------------------------------------------------------- /ajm/js/ajm.psui.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 18 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | */ 27 | 28 | // based on mymatrix.js from the tutorials 29 | 30 | //autowatch = 1 31 | //post("reloaded " + jsarguments[0] + " at " + new Date()) 32 | 33 | inlets = 0 34 | outlets = 4 35 | setoutletassist(0, "recall number") 36 | setoutletassist(1, "store number") 37 | setoutletassist(2, "the queue") 38 | setoutletassist(3, "queue on/off") 39 | 40 | var MAX_ROW_COL = 32; 41 | 42 | var cols=4; // default columns 43 | var rows=4; // default rows 44 | var bgcolor = [0,0,0,1]; 45 | var offcolor = [.4,.4,.4,1]; 46 | var oncolor = [1,1,1,1]; 47 | var textcolor = [0,0,0,1]; 48 | var storecolor = [1,.2,.2,1]; 49 | var queuecolor = [1,1,0,1]; 50 | var activeIdx = 0; 51 | var storingIdx = null; 52 | var storing = false; 53 | 54 | var redraw = new Task(function(){storingIdx = null;draw();}, this); 55 | var idxQueue = new Array(); 56 | var enqueue = false; 57 | 58 | declareattribute("cols","getcols","setcols"); 59 | declareattribute("rows","getrows","setrows"); 60 | if(jsarguments.length > 1) { 61 | setcols(jsarguments[1]); 62 | } 63 | if(jsarguments.length > 2) { 64 | setrows(jsarguments[2]); 65 | } 66 | sketch.default2d(); 67 | draw(); 68 | 69 | function getrows() { 70 | return rows; 71 | } 72 | function setrows(val) { 73 | if(arguments.length) { 74 | var n = arguments[0]; 75 | if(n < 1 || n > MAX_ROW_COL) { 76 | post("rows must be between 1 and " + MAX_ROW_COL + " (inclusive)\n"); 77 | } else { 78 | rows=arguments[0]; 79 | draw(); 80 | } 81 | } 82 | } 83 | 84 | function getcols() { 85 | return cols; 86 | } 87 | function setcols(val) { 88 | if(arguments.length) { 89 | var n = arguments[0]; 90 | if(n < 1 || n > MAX_ROW_COL) { 91 | post("cols must be between 1 and " + MAX_ROW_COL + " (inclusive)\n"); 92 | } else { 93 | cols=arguments[0]; 94 | draw(); 95 | } 96 | } 97 | } 98 | 99 | function brgb(r,g,b) { 100 | setcolor(bgcolor, r, g, b); 101 | } 102 | function offrgb(r,g,b) { 103 | setcolor(offcolor, r, g, b); 104 | } 105 | function onrgb(r,g,b) { 106 | setcolor(oncolor, r, g, b); 107 | } 108 | function textrgb(r,g,b) { 109 | setcolor(textcolor, r, g, b); 110 | } 111 | function storergb(r,g,b) { 112 | setcolor(storecolor, r, g, b); 113 | } 114 | function queuergb(r,g,b) { 115 | setcolor(queuecolor, r, g, b); 116 | } 117 | function setcolor(colorArray, r, g, b) { 118 | colorArray[0] = r/255.; 119 | colorArray[1] = g/255.; 120 | colorArray[2] = b/255.; 121 | draw(); 122 | } 123 | setcolor.local = 1; 124 | 125 | function draw() { 126 | with (sketch) { 127 | glclearcolor(bgcolor); 128 | glclear(); 129 | textalign("center","center"); 130 | font("Arial Black"); 131 | fontsize(10); 132 | colstep = 2./cols; 133 | rowstep = 2./rows; 134 | for(i=0;i 0) { 197 | activeIdx = idxQueue.shift() 198 | outputQueue() 199 | outlet(0, activeIdx) 200 | draw() 201 | } else { 202 | outputQueue() 203 | } 204 | } 205 | 206 | function queue(isEnabled) { 207 | enqueue = isEnabled 208 | if(!isEnabled) { 209 | // clear the queue when queuing is disabled 210 | idxQueue.splice(0) 211 | outputQueue() 212 | draw() 213 | } 214 | } 215 | 216 | function msg_int(idx) { 217 | recall(idx) 218 | } 219 | 220 | function set(idx) { 221 | activeIdx = idx 222 | draw() 223 | } 224 | 225 | function setq(args) { 226 | idxQueue = arrayfromargs(arguments) 227 | if(!enqueue & idxQueue.length > 0) { 228 | enqueue = true 229 | } 230 | //outputQueue() 231 | draw() 232 | } 233 | 234 | function store(idx) { 235 | activeIdx = idx 236 | storingIdx = idx 237 | outlet(1, activeIdx) 238 | draw() 239 | } 240 | 241 | function recall(idx) { 242 | if(enqueue) { 243 | idxQueue.push(idx) 244 | outputQueue() 245 | } else { 246 | activeIdx = idx; 247 | outlet(0, activeIdx); 248 | } 249 | draw(); 250 | } 251 | 252 | function outputQueue() { 253 | if(idxQueue.length > 0) { 254 | outlet(2, idxQueue) 255 | } else { 256 | outlet(2, " ") 257 | } 258 | } 259 | outputQueue.local = 1 260 | 261 | function ondblclick(x,y,button,mod1,shift) { 262 | onclick(x,y,button,mod1,shift) 263 | } 264 | ondblclick.local = 1 -------------------------------------------------------------------------------- /ajm/misc/ajm.bring-help-to-front-button.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 473.0, 217.0, 380.0, 348.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 473.0, 217.0, 380.0, 348.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 1, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "newobj", 22 | "text" : "prepend bgcolor", 23 | "fontname" : "Verdana", 24 | "numinlets" : 1, 25 | "patching_rect" : [ 76.0, 57.0, 101.0, 20.0 ], 26 | "fontsize" : 11.1, 27 | "numoutlets" : 1, 28 | "id" : "obj-10", 29 | "outlettype" : [ "" ] 30 | } 31 | 32 | } 33 | , { 34 | "box" : { 35 | "maxclass" : "newobj", 36 | "text" : "route bgcolor", 37 | "fontname" : "Verdana", 38 | "numinlets" : 1, 39 | "patching_rect" : [ 76.0, 30.0, 85.0, 20.0 ], 40 | "fontsize" : 11.1, 41 | "numoutlets" : 2, 42 | "id" : "obj-9", 43 | "outlettype" : [ "", "" ] 44 | } 45 | 46 | } 47 | , { 48 | "box" : { 49 | "maxclass" : "newobj", 50 | "text" : "patcherargs", 51 | "fontname" : "Verdana", 52 | "numinlets" : 1, 53 | "patching_rect" : [ 18.0, 4.0, 77.0, 20.0 ], 54 | "fontsize" : 11.1, 55 | "numoutlets" : 2, 56 | "id" : "obj-7", 57 | "outlettype" : [ "", "" ] 58 | } 59 | 60 | } 61 | , { 62 | "box" : { 63 | "maxclass" : "message", 64 | "text" : "#1", 65 | "fontname" : "Verdana", 66 | "presentation_rect" : [ 0.0, 0.0, 32.5, 18.0 ], 67 | "numinlets" : 2, 68 | "patching_rect" : [ 18.0, 98.0, 32.5, 18.0 ], 69 | "fontsize" : 11.1, 70 | "presentation" : 1, 71 | "numoutlets" : 1, 72 | "id" : "obj-5", 73 | "outlettype" : [ "" ] 74 | } 75 | 76 | } 77 | , { 78 | "box" : { 79 | "maxclass" : "message", 80 | "text" : "help #1", 81 | "fontname" : "Verdana", 82 | "numinlets" : 2, 83 | "patching_rect" : [ 151.0, 283.0, 54.0, 18.0 ], 84 | "fontsize" : 11.1, 85 | "numoutlets" : 1, 86 | "id" : "obj-6", 87 | "outlettype" : [ "" ] 88 | } 89 | 90 | } 91 | , { 92 | "box" : { 93 | "maxclass" : "newobj", 94 | "text" : "t 0", 95 | "fontname" : "Verdana", 96 | "numinlets" : 1, 97 | "patching_rect" : [ 151.0, 215.0, 26.0, 20.0 ], 98 | "fontsize" : 11.1, 99 | "numoutlets" : 1, 100 | "id" : "obj-20", 101 | "outlettype" : [ "int" ] 102 | } 103 | 104 | } 105 | , { 106 | "box" : { 107 | "maxclass" : "newobj", 108 | "text" : "t b b 1", 109 | "fontname" : "Verdana", 110 | "numinlets" : 1, 111 | "patching_rect" : [ 18.0, 131.0, 329.0, 20.0 ], 112 | "fontsize" : 11.1, 113 | "numoutlets" : 3, 114 | "id" : "obj-19", 115 | "outlettype" : [ "bang", "bang", "int" ] 116 | } 117 | 118 | } 119 | , { 120 | "box" : { 121 | "maxclass" : "newobj", 122 | "text" : "gate", 123 | "fontname" : "Verdana", 124 | "numinlets" : 2, 125 | "patching_rect" : [ 151.0, 258.0, 35.0, 20.0 ], 126 | "fontsize" : 11.1, 127 | "numoutlets" : 1, 128 | "id" : "obj-18", 129 | "outlettype" : [ "" ] 130 | } 131 | 132 | } 133 | , { 134 | "box" : { 135 | "maxclass" : "newobj", 136 | "text" : "s #1.bring_to_front", 137 | "fontname" : "Verdana", 138 | "numinlets" : 1, 139 | "patching_rect" : [ 108.0, 161.0, 121.0, 20.0 ], 140 | "fontsize" : 11.1, 141 | "numoutlets" : 0, 142 | "id" : "obj-15" 143 | } 144 | 145 | } 146 | , { 147 | "box" : { 148 | "maxclass" : "newobj", 149 | "text" : "pcontrol", 150 | "fontname" : "Verdana", 151 | "numinlets" : 1, 152 | "patching_rect" : [ 151.0, 309.0, 56.0, 20.0 ], 153 | "fontsize" : 11.1, 154 | "numoutlets" : 1, 155 | "id" : "obj-14", 156 | "outlettype" : [ "" ] 157 | } 158 | 159 | } 160 | , { 161 | "box" : { 162 | "maxclass" : "newobj", 163 | "text" : "r ajm.bring_to_front_callback", 164 | "fontname" : "Verdana", 165 | "numinlets" : 0, 166 | "patching_rect" : [ 151.0, 187.0, 177.0, 20.0 ], 167 | "fontsize" : 11.1, 168 | "numoutlets" : 1, 169 | "id" : "obj-13", 170 | "outlettype" : [ "" ] 171 | } 172 | 173 | } 174 | ], 175 | "lines" : [ { 176 | "patchline" : { 177 | "source" : [ "obj-10", 0 ], 178 | "destination" : [ "obj-5", 0 ], 179 | "hidden" : 0, 180 | "midpoints" : [ 85.5, 85.0, 27.5, 85.0 ] 181 | } 182 | 183 | } 184 | , { 185 | "patchline" : { 186 | "source" : [ "obj-9", 0 ], 187 | "destination" : [ "obj-10", 0 ], 188 | "hidden" : 0, 189 | "midpoints" : [ ] 190 | } 191 | 192 | } 193 | , { 194 | "patchline" : { 195 | "source" : [ "obj-7", 1 ], 196 | "destination" : [ "obj-9", 0 ], 197 | "hidden" : 0, 198 | "midpoints" : [ ] 199 | } 200 | 201 | } 202 | , { 203 | "patchline" : { 204 | "source" : [ "obj-5", 0 ], 205 | "destination" : [ "obj-19", 0 ], 206 | "hidden" : 0, 207 | "midpoints" : [ ] 208 | } 209 | 210 | } 211 | , { 212 | "patchline" : { 213 | "source" : [ "obj-19", 2 ], 214 | "destination" : [ "obj-18", 0 ], 215 | "hidden" : 0, 216 | "midpoints" : [ 337.5, 240.0, 160.5, 240.0 ] 217 | } 218 | 219 | } 220 | , { 221 | "patchline" : { 222 | "source" : [ "obj-20", 0 ], 223 | "destination" : [ "obj-18", 0 ], 224 | "hidden" : 0, 225 | "midpoints" : [ ] 226 | } 227 | 228 | } 229 | , { 230 | "patchline" : { 231 | "source" : [ "obj-19", 0 ], 232 | "destination" : [ "obj-18", 1 ], 233 | "hidden" : 0, 234 | "midpoints" : [ ] 235 | } 236 | 237 | } 238 | , { 239 | "patchline" : { 240 | "source" : [ "obj-19", 1 ], 241 | "destination" : [ "obj-15", 0 ], 242 | "hidden" : 0, 243 | "midpoints" : [ 182.5, 157.0, 117.5, 157.0 ] 244 | } 245 | 246 | } 247 | , { 248 | "patchline" : { 249 | "source" : [ "obj-13", 0 ], 250 | "destination" : [ "obj-20", 0 ], 251 | "hidden" : 0, 252 | "midpoints" : [ ] 253 | } 254 | 255 | } 256 | , { 257 | "patchline" : { 258 | "source" : [ "obj-18", 0 ], 259 | "destination" : [ "obj-6", 0 ], 260 | "hidden" : 0, 261 | "midpoints" : [ ] 262 | } 263 | 264 | } 265 | , { 266 | "patchline" : { 267 | "source" : [ "obj-6", 0 ], 268 | "destination" : [ "obj-14", 0 ], 269 | "hidden" : 0, 270 | "midpoints" : [ ] 271 | } 272 | 273 | } 274 | ] 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /ajm/misc/ajm.bring-to-front-listener.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 50.0, 94.0, 640.0, 506.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 50.0, 94.0, 640.0, 506.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "outlet", 22 | "hint" : "to thispatcher", 23 | "annotation" : "to thispatcher", 24 | "numinlets" : 1, 25 | "patching_rect" : [ 83.0, 77.0, 25.0, 25.0 ], 26 | "numoutlets" : 0, 27 | "id" : "obj-2", 28 | "comment" : "to thispatcher" 29 | } 30 | 31 | } 32 | , { 33 | "box" : { 34 | "maxclass" : "newobj", 35 | "text" : "t bang front", 36 | "fontname" : "Verdana", 37 | "numinlets" : 1, 38 | "patching_rect" : [ 25.0, 47.0, 77.0, 20.0 ], 39 | "fontsize" : 11.1, 40 | "numoutlets" : 2, 41 | "id" : "obj-7", 42 | "outlettype" : [ "bang", "front" ] 43 | } 44 | 45 | } 46 | , { 47 | "box" : { 48 | "maxclass" : "newobj", 49 | "text" : "s ajm.bring_to_front_callback", 50 | "fontname" : "Verdana", 51 | "numinlets" : 1, 52 | "patching_rect" : [ 25.0, 111.0, 178.0, 20.0 ], 53 | "fontsize" : 11.1, 54 | "numoutlets" : 0, 55 | "id" : "obj-5" 56 | } 57 | 58 | } 59 | , { 60 | "box" : { 61 | "maxclass" : "newobj", 62 | "varname" : "foo", 63 | "text" : "r #1.bring_to_front", 64 | "fontname" : "Verdana", 65 | "numinlets" : 0, 66 | "patching_rect" : [ 25.0, 18.0, 120.0, 20.0 ], 67 | "fontsize" : 11.1, 68 | "numoutlets" : 1, 69 | "id" : "obj-1", 70 | "outlettype" : [ "" ] 71 | } 72 | 73 | } 74 | ], 75 | "lines" : [ { 76 | "patchline" : { 77 | "source" : [ "obj-7", 1 ], 78 | "destination" : [ "obj-2", 0 ], 79 | "hidden" : 0, 80 | "midpoints" : [ ] 81 | } 82 | 83 | } 84 | , { 85 | "patchline" : { 86 | "source" : [ "obj-1", 0 ], 87 | "destination" : [ "obj-7", 0 ], 88 | "hidden" : 0, 89 | "midpoints" : [ ] 90 | } 91 | 92 | } 93 | , { 94 | "patchline" : { 95 | "source" : [ "obj-7", 0 ], 96 | "destination" : [ "obj-5", 0 ], 97 | "hidden" : 0, 98 | "midpoints" : [ ] 99 | } 100 | 101 | } 102 | ] 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /ajm/misc/ajm.bring-to-front.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 396.0, 91.0, 372.0, 284.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 396.0, 91.0, 372.0, 284.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "message", 22 | "text" : "load #1", 23 | "fontname" : "Verdana", 24 | "numinlets" : 2, 25 | "patching_rect" : [ 152.0, 208.0, 54.0, 18.0 ], 26 | "fontsize" : 11.1, 27 | "numoutlets" : 1, 28 | "id" : "obj-6", 29 | "outlettype" : [ "" ] 30 | } 31 | 32 | } 33 | , { 34 | "box" : { 35 | "maxclass" : "inlet", 36 | "numinlets" : 0, 37 | "patching_rect" : [ 19.0, 20.0, 25.0, 25.0 ], 38 | "numoutlets" : 1, 39 | "id" : "obj-3", 40 | "outlettype" : [ "" ], 41 | "comment" : "" 42 | } 43 | 44 | } 45 | , { 46 | "box" : { 47 | "maxclass" : "newobj", 48 | "text" : "t 0", 49 | "fontname" : "Verdana", 50 | "numinlets" : 1, 51 | "patching_rect" : [ 152.0, 134.0, 26.0, 20.0 ], 52 | "fontsize" : 11.1, 53 | "numoutlets" : 1, 54 | "id" : "obj-20", 55 | "outlettype" : [ "int" ] 56 | } 57 | 58 | } 59 | , { 60 | "box" : { 61 | "maxclass" : "newobj", 62 | "text" : "t b b 1", 63 | "fontname" : "Verdana", 64 | "numinlets" : 1, 65 | "patching_rect" : [ 19.0, 52.0, 329.0, 20.0 ], 66 | "fontsize" : 11.1, 67 | "numoutlets" : 3, 68 | "id" : "obj-19", 69 | "outlettype" : [ "bang", "bang", "int" ] 70 | } 71 | 72 | } 73 | , { 74 | "box" : { 75 | "maxclass" : "newobj", 76 | "text" : "gate", 77 | "fontname" : "Verdana", 78 | "numinlets" : 2, 79 | "patching_rect" : [ 152.0, 177.0, 35.0, 20.0 ], 80 | "fontsize" : 11.1, 81 | "numoutlets" : 1, 82 | "id" : "obj-18", 83 | "outlettype" : [ "" ] 84 | } 85 | 86 | } 87 | , { 88 | "box" : { 89 | "maxclass" : "newobj", 90 | "text" : "s #1.bring_to_front", 91 | "fontname" : "Verdana", 92 | "numinlets" : 1, 93 | "patching_rect" : [ 109.0, 82.0, 121.0, 20.0 ], 94 | "fontsize" : 11.1, 95 | "numoutlets" : 0, 96 | "id" : "obj-15" 97 | } 98 | 99 | } 100 | , { 101 | "box" : { 102 | "maxclass" : "newobj", 103 | "text" : "pcontrol", 104 | "fontname" : "Verdana", 105 | "numinlets" : 1, 106 | "patching_rect" : [ 152.0, 238.0, 56.0, 20.0 ], 107 | "fontsize" : 11.1, 108 | "numoutlets" : 1, 109 | "id" : "obj-14", 110 | "outlettype" : [ "" ] 111 | } 112 | 113 | } 114 | , { 115 | "box" : { 116 | "maxclass" : "newobj", 117 | "text" : "r ajm.bring_to_front_callback", 118 | "fontname" : "Verdana", 119 | "numinlets" : 0, 120 | "patching_rect" : [ 152.0, 106.0, 177.0, 20.0 ], 121 | "fontsize" : 11.1, 122 | "numoutlets" : 1, 123 | "id" : "obj-13", 124 | "outlettype" : [ "" ] 125 | } 126 | 127 | } 128 | ], 129 | "lines" : [ { 130 | "patchline" : { 131 | "source" : [ "obj-18", 0 ], 132 | "destination" : [ "obj-6", 0 ], 133 | "hidden" : 0, 134 | "midpoints" : [ ] 135 | } 136 | 137 | } 138 | , { 139 | "patchline" : { 140 | "source" : [ "obj-19", 2 ], 141 | "destination" : [ "obj-18", 0 ], 142 | "hidden" : 0, 143 | "midpoints" : [ 338.5, 159.0, 161.5, 159.0 ] 144 | } 145 | 146 | } 147 | , { 148 | "patchline" : { 149 | "source" : [ "obj-20", 0 ], 150 | "destination" : [ "obj-18", 0 ], 151 | "hidden" : 0, 152 | "midpoints" : [ ] 153 | } 154 | 155 | } 156 | , { 157 | "patchline" : { 158 | "source" : [ "obj-19", 0 ], 159 | "destination" : [ "obj-18", 1 ], 160 | "hidden" : 0, 161 | "midpoints" : [ ] 162 | } 163 | 164 | } 165 | , { 166 | "patchline" : { 167 | "source" : [ "obj-3", 0 ], 168 | "destination" : [ "obj-19", 0 ], 169 | "hidden" : 0, 170 | "midpoints" : [ ] 171 | } 172 | 173 | } 174 | , { 175 | "patchline" : { 176 | "source" : [ "obj-19", 1 ], 177 | "destination" : [ "obj-15", 0 ], 178 | "hidden" : 0, 179 | "midpoints" : [ 183.5, 78.0, 118.5, 78.0 ] 180 | } 181 | 182 | } 183 | , { 184 | "patchline" : { 185 | "source" : [ "obj-13", 0 ], 186 | "destination" : [ "obj-20", 0 ], 187 | "hidden" : 0, 188 | "midpoints" : [ ] 189 | } 190 | 191 | } 192 | , { 193 | "patchline" : { 194 | "source" : [ "obj-6", 0 ], 195 | "destination" : [ "obj-14", 0 ], 196 | "hidden" : 0, 197 | "midpoints" : [ ] 198 | } 199 | 200 | } 201 | ] 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /ajm/misc/ajm.envthispoly.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 10.0, 59.0, 281.0, 244.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 10.0, 59.0, 281.0, 244.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "newobj", 22 | "text" : "sel 0", 23 | "fontname" : "Verdana", 24 | "numinlets" : 2, 25 | "patching_rect" : [ 144.0, 44.0, 46.0, 20.0 ], 26 | "fontsize" : 11.0, 27 | "numoutlets" : 2, 28 | "id" : "obj-1", 29 | "outlettype" : [ "bang", "" ] 30 | } 31 | 32 | } 33 | , { 34 | "box" : { 35 | "maxclass" : "message", 36 | "text" : "1, mute 0", 37 | "fontname" : "Verdana", 38 | "numinlets" : 2, 39 | "patching_rect" : [ 171.0, 139.0, 65.0, 18.0 ], 40 | "fontsize" : 11.0, 41 | "numoutlets" : 1, 42 | "id" : "obj-2", 43 | "outlettype" : [ "" ] 44 | } 45 | 46 | } 47 | , { 48 | "box" : { 49 | "maxclass" : "message", 50 | "text" : "mute 1, 0", 51 | "fontname" : "Verdana", 52 | "numinlets" : 2, 53 | "patching_rect" : [ 59.0, 128.0, 65.0, 18.0 ], 54 | "fontsize" : 11.0, 55 | "numoutlets" : 1, 56 | "id" : "obj-3", 57 | "outlettype" : [ "" ] 58 | } 59 | 60 | } 61 | , { 62 | "box" : { 63 | "maxclass" : "comment", 64 | "text" : "make busy and unmute", 65 | "linecount" : 2, 66 | "fontname" : "Verdana", 67 | "numinlets" : 1, 68 | "patching_rect" : [ 182.0, 104.0, 75.0, 33.0 ], 69 | "fontsize" : 11.0, 70 | "numoutlets" : 0, 71 | "id" : "obj-4" 72 | } 73 | 74 | } 75 | , { 76 | "box" : { 77 | "maxclass" : "newobj", 78 | "text" : "loadbang", 79 | "fontname" : "Verdana", 80 | "numinlets" : 1, 81 | "patching_rect" : [ 59.0, 70.0, 59.0, 20.0 ], 82 | "fontsize" : 11.0, 83 | "numoutlets" : 1, 84 | "id" : "obj-5", 85 | "outlettype" : [ "bang" ] 86 | } 87 | 88 | } 89 | , { 90 | "box" : { 91 | "maxclass" : "comment", 92 | "text" : "mute and make available", 93 | "linecount" : 2, 94 | "fontname" : "Verdana", 95 | "numinlets" : 1, 96 | "patching_rect" : [ 68.0, 98.0, 92.0, 33.0 ], 97 | "fontsize" : 11.0, 98 | "numoutlets" : 0, 99 | "id" : "obj-6" 100 | } 101 | 102 | } 103 | , { 104 | "box" : { 105 | "maxclass" : "comment", 106 | "text" : "start playing:", 107 | "linecount" : 2, 108 | "fontname" : "Verdana", 109 | "numinlets" : 1, 110 | "patching_rect" : [ 182.0, 67.0, 53.0, 33.0 ], 111 | "fontsize" : 11.0, 112 | "numoutlets" : 0, 113 | "id" : "obj-7" 114 | } 115 | 116 | } 117 | , { 118 | "box" : { 119 | "maxclass" : "comment", 120 | "text" : "stop playing:", 121 | "linecount" : 2, 122 | "fontname" : "Verdana", 123 | "numinlets" : 1, 124 | "patching_rect" : [ 18.0, 96.0, 53.0, 33.0 ], 125 | "fontsize" : 11.0, 126 | "numoutlets" : 0, 127 | "id" : "obj-8" 128 | } 129 | 130 | } 131 | , { 132 | "box" : { 133 | "maxclass" : "inlet", 134 | "hint" : "from ajm.env~ right outlet", 135 | "annotation" : "from ajm.env~ right outlet", 136 | "numinlets" : 0, 137 | "patching_rect" : [ 144.0, 12.0, 22.0, 22.0 ], 138 | "numoutlets" : 1, 139 | "id" : "obj-9", 140 | "outlettype" : [ "" ], 141 | "comment" : "from ajm.env~ right outlet" 142 | } 143 | 144 | } 145 | , { 146 | "box" : { 147 | "maxclass" : "outlet", 148 | "hint" : "to thispoly~", 149 | "annotation" : "to thispoly~", 150 | "numinlets" : 1, 151 | "patching_rect" : [ 136.0, 191.0, 22.0, 22.0 ], 152 | "numoutlets" : 0, 153 | "id" : "obj-10", 154 | "comment" : "to thispoly~" 155 | } 156 | 157 | } 158 | ], 159 | "lines" : [ { 160 | "patchline" : { 161 | "source" : [ "obj-1", 1 ], 162 | "destination" : [ "obj-2", 0 ], 163 | "hidden" : 0, 164 | "midpoints" : [ ] 165 | } 166 | 167 | } 168 | , { 169 | "patchline" : { 170 | "source" : [ "obj-9", 0 ], 171 | "destination" : [ "obj-1", 0 ], 172 | "hidden" : 0, 173 | "midpoints" : [ ] 174 | } 175 | 176 | } 177 | , { 178 | "patchline" : { 179 | "source" : [ "obj-3", 0 ], 180 | "destination" : [ "obj-10", 0 ], 181 | "hidden" : 0, 182 | "midpoints" : [ 68.5, 172.0, 145.5, 172.0 ] 183 | } 184 | 185 | } 186 | , { 187 | "patchline" : { 188 | "source" : [ "obj-2", 0 ], 189 | "destination" : [ "obj-10", 0 ], 190 | "hidden" : 0, 191 | "midpoints" : [ 180.5, 172.0, 145.5, 172.0 ] 192 | } 193 | 194 | } 195 | , { 196 | "patchline" : { 197 | "source" : [ "obj-1", 0 ], 198 | "destination" : [ "obj-3", 0 ], 199 | "hidden" : 0, 200 | "midpoints" : [ 153.5, 95.0, 68.5, 95.0 ] 201 | } 202 | 203 | } 204 | , { 205 | "patchline" : { 206 | "source" : [ "obj-5", 0 ], 207 | "destination" : [ "obj-3", 0 ], 208 | "hidden" : 0, 209 | "midpoints" : [ ] 210 | } 211 | 212 | } 213 | ] 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /ajm/misc/ajm.polymakenote.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 61.0, 62.0, 323.0, 254.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 61.0, 62.0, 323.0, 254.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "newobj", 22 | "text" : "makenote", 23 | "fontname" : "Verdana", 24 | "numinlets" : 3, 25 | "numoutlets" : 2, 26 | "patching_rect" : [ 18.0, 52.0, 63.0, 20.0 ], 27 | "fontsize" : 11.0, 28 | "outlettype" : [ "int", "int" ], 29 | "id" : "obj-1" 30 | } 31 | 32 | } 33 | , { 34 | "box" : { 35 | "maxclass" : "newobj", 36 | "text" : "out 1", 37 | "fontname" : "Verdana", 38 | "numinlets" : 1, 39 | "numoutlets" : 0, 40 | "patching_rect" : [ 21.0, 191.0, 41.0, 20.0 ], 41 | "fontsize" : 11.0, 42 | "id" : "obj-2", 43 | "saved_object_attributes" : { 44 | "attr_comment" : "" 45 | } 46 | 47 | } 48 | 49 | } 50 | , { 51 | "box" : { 52 | "maxclass" : "comment", 53 | "text" : "for mutemap & busymap (required for ajm.busymap)", 54 | "linecount" : 2, 55 | "fontname" : "Verdana", 56 | "numinlets" : 1, 57 | "numoutlets" : 0, 58 | "patching_rect" : [ 71.0, 188.0, 163.0, 33.0 ], 59 | "fontsize" : 11.0, 60 | "id" : "obj-3" 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "maxclass" : "newobj", 67 | "text" : "!= 0", 68 | "fontname" : "Verdana", 69 | "numinlets" : 2, 70 | "numoutlets" : 1, 71 | "patching_rect" : [ 62.0, 85.0, 33.0, 20.0 ], 72 | "fontsize" : 11.0, 73 | "outlettype" : [ "int" ], 74 | "id" : "obj-4" 75 | } 76 | 77 | } 78 | , { 79 | "box" : { 80 | "maxclass" : "newobj", 81 | "text" : "thispoly~", 82 | "fontname" : "Verdana", 83 | "numinlets" : 1, 84 | "numoutlets" : 2, 85 | "patching_rect" : [ 62.0, 118.0, 64.0, 20.0 ], 86 | "fontsize" : 11.0, 87 | "outlettype" : [ "int", "int" ], 88 | "id" : "obj-5" 89 | } 90 | 91 | } 92 | , { 93 | "box" : { 94 | "maxclass" : "newobj", 95 | "text" : "in 1", 96 | "fontname" : "Verdana", 97 | "numinlets" : 1, 98 | "numoutlets" : 1, 99 | "patching_rect" : [ 18.0, 19.0, 29.0, 20.0 ], 100 | "fontsize" : 11.0, 101 | "outlettype" : [ "" ], 102 | "id" : "obj-6", 103 | "saved_object_attributes" : { 104 | "attr_comment" : "" 105 | } 106 | 107 | } 108 | 109 | } 110 | , { 111 | "box" : { 112 | "maxclass" : "comment", 113 | "text" : "make busy if non-zero velocity, then available after note off", 114 | "linecount" : 2, 115 | "fontname" : "Verdana", 116 | "numinlets" : 1, 117 | "numoutlets" : 0, 118 | "patching_rect" : [ 101.0, 80.0, 201.0, 33.0 ], 119 | "fontsize" : 11.0, 120 | "id" : "obj-7" 121 | } 122 | 123 | } 124 | ], 125 | "lines" : [ { 126 | "patchline" : { 127 | "source" : [ "obj-4", 0 ], 128 | "destination" : [ "obj-5", 0 ], 129 | "hidden" : 0, 130 | "midpoints" : [ ] 131 | } 132 | 133 | } 134 | , { 135 | "patchline" : { 136 | "source" : [ "obj-1", 1 ], 137 | "destination" : [ "obj-4", 0 ], 138 | "hidden" : 0, 139 | "midpoints" : [ ] 140 | } 141 | 142 | } 143 | , { 144 | "patchline" : { 145 | "source" : [ "obj-6", 0 ], 146 | "destination" : [ "obj-1", 0 ], 147 | "hidden" : 0, 148 | "midpoints" : [ ] 149 | } 150 | 151 | } 152 | ] 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /ajm/misc/ajm.polyprint.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 118.0, 58.0, 230.0, 310.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 118.0, 58.0, 230.0, 310.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "newobj", 22 | "text" : "out 1", 23 | "patching_rect" : [ 140.0, 230.0, 33.0, 17.0 ], 24 | "id" : "obj-1", 25 | "fontname" : "Verdana", 26 | "fontsize" : 9.0, 27 | "numinlets" : 1, 28 | "numoutlets" : 0, 29 | "saved_object_attributes" : { 30 | "attr_comment" : "" 31 | } 32 | 33 | } 34 | 35 | } 36 | , { 37 | "box" : { 38 | "maxclass" : "comment", 39 | "text" : "for mutemap & busymap", 40 | "linecount" : 2, 41 | "patching_rect" : [ 142.0, 199.0, 70.0, 28.0 ], 42 | "id" : "obj-2", 43 | "fontname" : "Verdana", 44 | "fontsize" : 9.0, 45 | "numinlets" : 1, 46 | "numoutlets" : 0 47 | } 48 | 49 | } 50 | , { 51 | "box" : { 52 | "maxclass" : "newobj", 53 | "text" : "t 0 1", 54 | "patching_rect" : [ 33.0, 61.0, 32.0, 17.0 ], 55 | "outlettype" : [ "int", "int" ], 56 | "id" : "obj-3", 57 | "fontname" : "Verdana", 58 | "fontsize" : 9.0, 59 | "numinlets" : 1, 60 | "numoutlets" : 2 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "maxclass" : "newobj", 67 | "text" : "pipe 10", 68 | "patching_rect" : [ 33.0, 88.0, 43.0, 17.0 ], 69 | "outlettype" : [ "" ], 70 | "id" : "obj-4", 71 | "fontname" : "Verdana", 72 | "fontsize" : 9.0, 73 | "numinlets" : 2, 74 | "numoutlets" : 1 75 | } 76 | 77 | } 78 | , { 79 | "box" : { 80 | "maxclass" : "newobj", 81 | "text" : "prepend voice", 82 | "patching_rect" : [ 18.0, 203.0, 72.0, 17.0 ], 83 | "outlettype" : [ "" ], 84 | "id" : "obj-5", 85 | "fontname" : "Verdana", 86 | "fontsize" : 9.0, 87 | "numinlets" : 1, 88 | "numoutlets" : 1 89 | } 90 | 91 | } 92 | , { 93 | "box" : { 94 | "maxclass" : "newobj", 95 | "text" : "loadbang", 96 | "patching_rect" : [ 101.0, 86.0, 50.0, 17.0 ], 97 | "outlettype" : [ "bang" ], 98 | "id" : "obj-6", 99 | "fontname" : "Verdana", 100 | "fontsize" : 9.0, 101 | "numinlets" : 1, 102 | "numoutlets" : 1 103 | } 104 | 105 | } 106 | , { 107 | "box" : { 108 | "maxclass" : "message", 109 | "text" : "set $1:", 110 | "patching_rect" : [ 87.0, 140.0, 43.0, 15.0 ], 111 | "outlettype" : [ "" ], 112 | "id" : "obj-7", 113 | "fontname" : "Verdana", 114 | "fontsize" : 9.0, 115 | "numinlets" : 2, 116 | "numoutlets" : 1 117 | } 118 | 119 | } 120 | , { 121 | "box" : { 122 | "maxclass" : "newobj", 123 | "text" : "prepend unknown:", 124 | "patching_rect" : [ 18.0, 179.0, 94.0, 17.0 ], 125 | "outlettype" : [ "" ], 126 | "id" : "obj-8", 127 | "fontname" : "Verdana", 128 | "fontsize" : 9.0, 129 | "numinlets" : 1, 130 | "numoutlets" : 1 131 | } 132 | 133 | } 134 | , { 135 | "box" : { 136 | "maxclass" : "newobj", 137 | "text" : "thispoly~", 138 | "patching_rect" : [ 87.0, 117.0, 52.0, 17.0 ], 139 | "outlettype" : [ "int", "int" ], 140 | "id" : "obj-9", 141 | "fontname" : "Verdana", 142 | "fontsize" : 9.0, 143 | "numinlets" : 1, 144 | "numoutlets" : 2 145 | } 146 | 147 | } 148 | , { 149 | "box" : { 150 | "maxclass" : "newobj", 151 | "text" : "print polyprint", 152 | "patching_rect" : [ 18.0, 227.0, 76.0, 17.0 ], 153 | "id" : "obj-10", 154 | "fontname" : "Verdana", 155 | "fontsize" : 9.0, 156 | "numinlets" : 1, 157 | "numoutlets" : 0 158 | } 159 | 160 | } 161 | , { 162 | "box" : { 163 | "maxclass" : "newobj", 164 | "text" : "in 1", 165 | "patching_rect" : [ 18.0, 28.0, 26.0, 17.0 ], 166 | "outlettype" : [ "" ], 167 | "id" : "obj-11", 168 | "fontname" : "Verdana", 169 | "fontsize" : 9.0, 170 | "numinlets" : 1, 171 | "numoutlets" : 1, 172 | "saved_object_attributes" : { 173 | "attr_comment" : "" 174 | } 175 | 176 | } 177 | 178 | } 179 | , { 180 | "box" : { 181 | "maxclass" : "comment", 182 | "text" : "make busy briefly", 183 | "patching_rect" : [ 68.0, 59.0, 100.0, 17.0 ], 184 | "id" : "obj-12", 185 | "fontname" : "Verdana", 186 | "fontsize" : 9.0, 187 | "numinlets" : 1, 188 | "numoutlets" : 0 189 | } 190 | 191 | } 192 | ], 193 | "lines" : [ { 194 | "patchline" : { 195 | "source" : [ "obj-9", 0 ], 196 | "destination" : [ "obj-7", 0 ], 197 | "hidden" : 0, 198 | "midpoints" : [ ] 199 | } 200 | 201 | } 202 | , { 203 | "patchline" : { 204 | "source" : [ "obj-4", 0 ], 205 | "destination" : [ "obj-9", 0 ], 206 | "hidden" : 0, 207 | "midpoints" : [ 42.5, 110.0, 96.5, 110.0 ] 208 | } 209 | 210 | } 211 | , { 212 | "patchline" : { 213 | "source" : [ "obj-3", 1 ], 214 | "destination" : [ "obj-9", 0 ], 215 | "hidden" : 0, 216 | "midpoints" : [ 55.5, 81.0, 96.5, 81.0 ] 217 | } 218 | 219 | } 220 | , { 221 | "patchline" : { 222 | "source" : [ "obj-6", 0 ], 223 | "destination" : [ "obj-9", 0 ], 224 | "hidden" : 0, 225 | "midpoints" : [ ] 226 | } 227 | 228 | } 229 | , { 230 | "patchline" : { 231 | "source" : [ "obj-3", 0 ], 232 | "destination" : [ "obj-4", 0 ], 233 | "hidden" : 0, 234 | "midpoints" : [ ] 235 | } 236 | 237 | } 238 | , { 239 | "patchline" : { 240 | "source" : [ "obj-11", 0 ], 241 | "destination" : [ "obj-3", 0 ], 242 | "hidden" : 0, 243 | "midpoints" : [ ] 244 | } 245 | 246 | } 247 | , { 248 | "patchline" : { 249 | "source" : [ "obj-5", 0 ], 250 | "destination" : [ "obj-10", 0 ], 251 | "hidden" : 0, 252 | "midpoints" : [ ] 253 | } 254 | 255 | } 256 | , { 257 | "patchline" : { 258 | "source" : [ "obj-8", 0 ], 259 | "destination" : [ "obj-5", 0 ], 260 | "hidden" : 0, 261 | "midpoints" : [ ] 262 | } 263 | 264 | } 265 | , { 266 | "patchline" : { 267 | "source" : [ "obj-7", 0 ], 268 | "destination" : [ "obj-8", 0 ], 269 | "hidden" : 0, 270 | "midpoints" : [ ] 271 | } 272 | 273 | } 274 | , { 275 | "patchline" : { 276 | "source" : [ "obj-11", 0 ], 277 | "destination" : [ "obj-8", 0 ], 278 | "hidden" : 0, 279 | "midpoints" : [ ] 280 | } 281 | 282 | } 283 | ] 284 | } 285 | 286 | } 287 | -------------------------------------------------------------------------------- /ajm/misc/ajm.translate-duration.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 50.0, 94.0, 362.0, 256.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 50.0, 94.0, 362.0, 256.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "outlet", 22 | "numoutlets" : 0, 23 | "patching_rect" : [ 121.0, 192.0, 25.0, 25.0 ], 24 | "id" : "obj-7", 25 | "numinlets" : 1, 26 | "comment" : "" 27 | } 28 | 29 | } 30 | , { 31 | "box" : { 32 | "maxclass" : "inlet", 33 | "numoutlets" : 1, 34 | "outlettype" : [ "" ], 35 | "patching_rect" : [ 171.0, 33.0, 25.0, 25.0 ], 36 | "id" : "obj-5", 37 | "numinlets" : 0, 38 | "presentation_rect" : [ 202.0, 80.0, 0.0, 0.0 ], 39 | "comment" : "" 40 | } 41 | 42 | } 43 | , { 44 | "box" : { 45 | "maxclass" : "newobj", 46 | "text" : "route int float", 47 | "numoutlets" : 3, 48 | "fontname" : "Verdana", 49 | "outlettype" : [ "", "", "" ], 50 | "patching_rect" : [ 87.0, 77.0, 87.0, 20.0 ], 51 | "id" : "obj-4", 52 | "fontsize" : 11.1, 53 | "numinlets" : 1 54 | } 55 | 56 | } 57 | , { 58 | "box" : { 59 | "maxclass" : "newobj", 60 | "text" : "translate @out ticks", 61 | "numoutlets" : 1, 62 | "fontname" : "Verdana", 63 | "outlettype" : [ "" ], 64 | "patching_rect" : [ 171.0, 134.0, 122.0, 20.0 ], 65 | "id" : "obj-1", 66 | "fontsize" : 11.1, 67 | "numinlets" : 1 68 | } 69 | 70 | } 71 | , { 72 | "box" : { 73 | "maxclass" : "inlet", 74 | "numoutlets" : 1, 75 | "outlettype" : [ "" ], 76 | "patching_rect" : [ 87.0, 32.0, 25.0, 25.0 ], 77 | "id" : "obj-2", 78 | "numinlets" : 0, 79 | "comment" : "" 80 | } 81 | 82 | } 83 | ], 84 | "lines" : [ { 85 | "patchline" : { 86 | "source" : [ "obj-1", 0 ], 87 | "destination" : [ "obj-7", 0 ], 88 | "hidden" : 0, 89 | "midpoints" : [ ] 90 | } 91 | 92 | } 93 | , { 94 | "patchline" : { 95 | "source" : [ "obj-4", 2 ], 96 | "destination" : [ "obj-1", 0 ], 97 | "hidden" : 0, 98 | "midpoints" : [ ] 99 | } 100 | 101 | } 102 | , { 103 | "patchline" : { 104 | "source" : [ "obj-4", 1 ], 105 | "destination" : [ "obj-7", 0 ], 106 | "hidden" : 0, 107 | "midpoints" : [ ] 108 | } 109 | 110 | } 111 | , { 112 | "patchline" : { 113 | "source" : [ "obj-4", 0 ], 114 | "destination" : [ "obj-7", 0 ], 115 | "hidden" : 0, 116 | "midpoints" : [ ] 117 | } 118 | 119 | } 120 | , { 121 | "patchline" : { 122 | "source" : [ "obj-5", 0 ], 123 | "destination" : [ "obj-1", 0 ], 124 | "hidden" : 0, 125 | "midpoints" : [ ] 126 | } 127 | 128 | } 129 | , { 130 | "patchline" : { 131 | "source" : [ "obj-2", 0 ], 132 | "destination" : [ "obj-4", 0 ], 133 | "hidden" : 0, 134 | "midpoints" : [ ] 135 | } 136 | 137 | } 138 | ] 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /ajm/misc/ajm.translate-line.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 25.0, 69.0, 354.0, 353.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 25.0, 69.0, 354.0, 353.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "outlet", 22 | "numinlets" : 1, 23 | "patching_rect" : [ 61.0, 297.0, 25.0, 25.0 ], 24 | "numoutlets" : 0, 25 | "id" : "obj-41", 26 | "comment" : "" 27 | } 28 | 29 | } 30 | , { 31 | "box" : { 32 | "maxclass" : "newobj", 33 | "text" : "prepend transport", 34 | "fontname" : "Verdana", 35 | "numinlets" : 1, 36 | "patching_rect" : [ 148.0, 134.0, 111.0, 20.0 ], 37 | "fontsize" : 11.1, 38 | "numoutlets" : 1, 39 | "id" : "obj-39", 40 | "outlettype" : [ "" ] 41 | } 42 | 43 | } 44 | , { 45 | "box" : { 46 | "maxclass" : "inlet", 47 | "numinlets" : 0, 48 | "patching_rect" : [ 43.0, 18.0, 25.0, 25.0 ], 49 | "numoutlets" : 1, 50 | "id" : "obj-36", 51 | "outlettype" : [ "" ], 52 | "comment" : "" 53 | } 54 | 55 | } 56 | , { 57 | "box" : { 58 | "maxclass" : "newobj", 59 | "text" : "route transport", 60 | "fontname" : "Verdana", 61 | "numinlets" : 1, 62 | "patching_rect" : [ 148.0, 77.0, 95.0, 20.0 ], 63 | "fontsize" : 11.1, 64 | "numoutlets" : 2, 65 | "id" : "obj-32", 66 | "outlettype" : [ "", "" ] 67 | } 68 | 69 | } 70 | , { 71 | "box" : { 72 | "maxclass" : "newobj", 73 | "text" : "patcherargs", 74 | "fontname" : "Verdana", 75 | "numinlets" : 1, 76 | "patching_rect" : [ 90.0, 22.0, 77.0, 20.0 ], 77 | "fontsize" : 11.1, 78 | "numoutlets" : 2, 79 | "id" : "obj-31", 80 | "outlettype" : [ "", "" ] 81 | } 82 | 83 | } 84 | , { 85 | "box" : { 86 | "maxclass" : "newobj", 87 | "text" : "route int float", 88 | "fontname" : "Verdana", 89 | "numinlets" : 1, 90 | "patching_rect" : [ 61.0, 202.0, 87.0, 20.0 ], 91 | "fontsize" : 11.1, 92 | "numoutlets" : 3, 93 | "id" : "obj-30", 94 | "outlettype" : [ "", "", "" ] 95 | } 96 | 97 | } 98 | , { 99 | "box" : { 100 | "maxclass" : "newobj", 101 | "text" : "t b l", 102 | "fontname" : "Verdana", 103 | "numinlets" : 1, 104 | "patching_rect" : [ 47.0, 148.0, 33.0, 20.0 ], 105 | "fontsize" : 11.1, 106 | "numoutlets" : 2, 107 | "id" : "obj-13", 108 | "outlettype" : [ "bang", "" ] 109 | } 110 | 111 | } 112 | , { 113 | "box" : { 114 | "maxclass" : "newobj", 115 | "text" : "zl group 1024", 116 | "fontname" : "Verdana", 117 | "presentation_rect" : [ 259.0, 330.0, 0.0, 0.0 ], 118 | "numinlets" : 2, 119 | "patching_rect" : [ 61.0, 266.0, 88.0, 20.0 ], 120 | "fontsize" : 11.1, 121 | "numoutlets" : 2, 122 | "id" : "obj-12", 123 | "outlettype" : [ "", "" ] 124 | } 125 | 126 | } 127 | , { 128 | "box" : { 129 | "maxclass" : "newobj", 130 | "text" : "zl iter 1", 131 | "fontname" : "Verdana", 132 | "numinlets" : 2, 133 | "patching_rect" : [ 61.0, 175.0, 53.0, 20.0 ], 134 | "fontsize" : 11.1, 135 | "numoutlets" : 2, 136 | "id" : "obj-2", 137 | "outlettype" : [ "", "" ] 138 | } 139 | 140 | } 141 | , { 142 | "box" : { 143 | "maxclass" : "newobj", 144 | "text" : "translate @in notevalues @out ms", 145 | "fontname" : "Verdana", 146 | "numinlets" : 1, 147 | "patching_rect" : [ 129.0, 232.0, 203.0, 20.0 ], 148 | "fontsize" : 11.1, 149 | "numoutlets" : 1, 150 | "id" : "obj-1", 151 | "outlettype" : [ "" ] 152 | } 153 | 154 | } 155 | ], 156 | "lines" : [ { 157 | "patchline" : { 158 | "source" : [ "obj-12", 0 ], 159 | "destination" : [ "obj-41", 0 ], 160 | "hidden" : 0, 161 | "midpoints" : [ ] 162 | } 163 | 164 | } 165 | , { 166 | "patchline" : { 167 | "source" : [ "obj-39", 0 ], 168 | "destination" : [ "obj-1", 0 ], 169 | "hidden" : 0, 170 | "midpoints" : [ 157.5, 226.0, 138.5, 226.0 ] 171 | } 172 | 173 | } 174 | , { 175 | "patchline" : { 176 | "source" : [ "obj-32", 0 ], 177 | "destination" : [ "obj-39", 0 ], 178 | "hidden" : 0, 179 | "midpoints" : [ ] 180 | } 181 | 182 | } 183 | , { 184 | "patchline" : { 185 | "source" : [ "obj-32", 1 ], 186 | "destination" : [ "obj-13", 0 ], 187 | "hidden" : 0, 188 | "midpoints" : [ ] 189 | } 190 | 191 | } 192 | , { 193 | "patchline" : { 194 | "source" : [ "obj-36", 0 ], 195 | "destination" : [ "obj-32", 0 ], 196 | "hidden" : 0, 197 | "midpoints" : [ 52.5, 66.0, 157.5, 66.0 ] 198 | } 199 | 200 | } 201 | , { 202 | "patchline" : { 203 | "source" : [ "obj-31", 1 ], 204 | "destination" : [ "obj-32", 0 ], 205 | "hidden" : 0, 206 | "midpoints" : [ ] 207 | } 208 | 209 | } 210 | , { 211 | "patchline" : { 212 | "source" : [ "obj-30", 1 ], 213 | "destination" : [ "obj-12", 0 ], 214 | "hidden" : 0, 215 | "midpoints" : [ 104.5, 259.0, 70.5, 259.0 ] 216 | } 217 | 218 | } 219 | , { 220 | "patchline" : { 221 | "source" : [ "obj-30", 0 ], 222 | "destination" : [ "obj-12", 0 ], 223 | "hidden" : 0, 224 | "midpoints" : [ ] 225 | } 226 | 227 | } 228 | , { 229 | "patchline" : { 230 | "source" : [ "obj-30", 2 ], 231 | "destination" : [ "obj-1", 0 ], 232 | "hidden" : 0, 233 | "midpoints" : [ ] 234 | } 235 | 236 | } 237 | , { 238 | "patchline" : { 239 | "source" : [ "obj-2", 0 ], 240 | "destination" : [ "obj-30", 0 ], 241 | "hidden" : 0, 242 | "midpoints" : [ ] 243 | } 244 | 245 | } 246 | , { 247 | "patchline" : { 248 | "source" : [ "obj-13", 1 ], 249 | "destination" : [ "obj-2", 0 ], 250 | "hidden" : 0, 251 | "midpoints" : [ ] 252 | } 253 | 254 | } 255 | , { 256 | "patchline" : { 257 | "source" : [ "obj-13", 0 ], 258 | "destination" : [ "obj-12", 0 ], 259 | "hidden" : 0, 260 | "midpoints" : [ 56.5, 259.0, 70.5, 259.0 ] 261 | } 262 | 263 | } 264 | , { 265 | "patchline" : { 266 | "source" : [ "obj-1", 0 ], 267 | "destination" : [ "obj-12", 0 ], 268 | "hidden" : 0, 269 | "midpoints" : [ 138.5, 259.0, 70.5, 259.0 ] 270 | } 271 | 272 | } 273 | ] 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /ajm/ruby/ajm_cosy.rb: -------------------------------------------------------------------------------- 1 | require 'cosy' 2 | include Cosy 3 | 4 | OBJECT_NAME = 'ajm.cosy' 5 | TICKS_PER_QUARTER_NOTE = 480 6 | 7 | module Cosy 8 | 9 | class MaxRenderer < AbstractRenderer 10 | attr_accessor :seq, :time_to_next, :prev_duration, :end, :ticks_per_bang 11 | 12 | def initialize 13 | super({:input => ''}) 14 | restart 15 | @ticks_per_bang = DURATION['sixtyfourth'].to_f 16 | end 17 | 18 | def sequence(cosy_syntax) 19 | begin 20 | parse cosy_syntax 21 | restart 22 | return true 23 | rescue Exception 24 | error "#{OBJECT_NAME}: #{$!}" 25 | return false 26 | end 27 | end 28 | 29 | def define(name, cosy_syntax) 30 | begin 31 | define_sequence(name, cosy_syntax) 32 | return true 33 | rescue Exception 34 | error "#{OBJECT_NAME}: #{$!}" 35 | return false 36 | end 37 | end 38 | 39 | def play(name) 40 | begin 41 | load_sequence(name) 42 | restart 43 | rescue Exception 44 | error "#{OBJECT_NAME}: #{$!}" 45 | end 46 | end 47 | 48 | def restart 49 | init 50 | @sequencer.restart 51 | @time_to_next = 1 52 | @prev_duration = DURATION['sixtyfourth'] 53 | @end = false 54 | @suppress_rebang = @rebang = false 55 | end 56 | 57 | # like restart but with inifinite loop prevention 58 | # should be used whenever automatically restarting at the end of a sequence 59 | # TODO: maybe loops are better handled in here 60 | def autorestart 61 | rebang = @rebang 62 | restart 63 | @suppress_rebang = rebang 64 | end 65 | 66 | def ticks_to_bangs(ticks) 67 | ticks / @ticks_per_bang if ticks 68 | end 69 | 70 | def bang 71 | if not @end 72 | @time_to_next -= 1 73 | if @time_to_next <= 0 74 | event = next_event 75 | 76 | if not event 77 | @end = true 78 | out4 'bang' 79 | 80 | elsif event.is_a? NoteEvent 81 | pitches, velocity, duration = event.pitches, event.velocity, ticks_to_bangs(event.duration) 82 | if duration >= 0 83 | # output in standard Max right-to-left order: 84 | out2 duration 85 | out1 velocity 86 | out0 pitches 87 | 88 | if duration == 0 89 | # prevent infinite loops 90 | if not @suppress_rebang 91 | @rebang = true 92 | bang 93 | else 94 | @suppress_rebang = false 95 | end 96 | else 97 | @suppress_rebang = @rebang = false 98 | end 99 | 100 | end 101 | @time_to_next = duration.abs 102 | 103 | else 104 | if event.is_a? Chain 105 | # this is kind of nasty, but I don't want to try to flatten 106 | # things in this way in AbstractRenderer#next_event because 107 | # other renderers may need to interpret Labels or 108 | # do handle chords of non-pitches differently 109 | raw = event 110 | event = [] 111 | raw.each do |e| 112 | case e 113 | when Label then event.insert(0, e.value) 114 | when Value then event << e.value 115 | when Chord then event += e 116 | else event << e 117 | end 118 | end 119 | end 120 | out3 event 121 | end 122 | 123 | end 124 | end 125 | end 126 | 127 | end 128 | end 129 | 130 | RENDERER = Cosy::MaxRenderer.new 131 | 132 | ################################################ 133 | # The interface for Max (the supported messages) 134 | 135 | def sequence(input) 136 | out5 RENDERER.sequence(input) 137 | end 138 | 139 | def define(name, input) 140 | out5 RENDERER.define(name, input) 141 | end 142 | 143 | def play(name) 144 | RENDERER.play(name) 145 | end 146 | 147 | def restart 148 | RENDERER.restart 149 | end 150 | 151 | def autorestart 152 | RENDERER.autorestart 153 | end 154 | 155 | def bang 156 | RENDERER.bang 157 | end 158 | 159 | inlet_assist 'sequence/define/play' 160 | outlet_assist 'pitch','velocity','duration','other','bang when done','parsed successfully?' 161 | -------------------------------------------------------------------------------- /ajm/ruby/ajm_cosy2coll.rb: -------------------------------------------------------------------------------- 1 | $DEBUG=true 2 | require 'cosy' 3 | include Cosy 4 | 5 | OBJECT_NAME = 'ajm.cosy' 6 | TICKS_PER_QUARTER_NOTE = 480 7 | 8 | def render(input) 9 | Cosy::CollRenderer.new({:input => input}).render 10 | rescue Exception => e 11 | puts e 12 | end 13 | 14 | inlet_assist 'cosy sequence' 15 | outlet_assist 'to midi coll','other messages','bang when done','parsed successfully?' 16 | 17 | module Cosy 18 | 19 | class CollRenderer < AbstractRenderer 20 | 21 | def initialize(options={}) 22 | super 23 | @ticks_per_beat = (4.0/options.fetch(:beat_unit, 4) * TICKS_PER_QUARTER_NOTE).to_i 24 | @beats_per_bar = options.fetch(:beats_per_bar, 4) 25 | @quantize_in_ticks = options[:quantize] 26 | end 27 | 28 | def render() 29 | out0 'clear' 30 | @timeline.times.each do |time| 31 | events = @timeline[time] 32 | start_coll_entry(time) 33 | events.each do |event| 34 | case event 35 | when Event::Note 36 | note(event.pitch, event.velocity, event.duration) 37 | end 38 | end 39 | end_coll_entry 40 | end 41 | out2 'bang' 42 | end 43 | 44 | 45 | ################# 46 | protected 47 | 48 | def time 49 | @time 50 | end 51 | 52 | 53 | ################# 54 | private 55 | 56 | def start_coll_entry(time) 57 | @coll_entry = ['store', time] # the index for [coll] 58 | # we could optionally do ticks to bbu here 59 | end 60 | 61 | def end_coll_entry 62 | puts @coll_entry.inspect 63 | out0 @coll_entry 64 | @coll_entry = nil 65 | end 66 | 67 | def note(pitch, velocity, duration) 68 | duration_in_beats = duration.to_f/@ticks_per_beat 69 | duration_in_beats = (duration_in_beats*1000).round / 1000.0 # limit digits for readability 70 | @coll_entry << "#{pitch} #{velocity} #{duration_in_beats}" 71 | end 72 | 73 | def ticks_to_bbu(ticks) 74 | ticks = ticks.to_i 75 | if @quantize 76 | diff = ticks % @quantize 77 | ticks -= diff 78 | if diff >= @quantize / 2 79 | ticks += @quantize 80 | end 81 | end 82 | units = ticks % @ticks_per_beat 83 | beats = ticks / @ticks_per_beat # total beat offset 84 | bars = beats / @beats_per_bar + 1 85 | beats = beats % @beats_per_bar + 1 # beats relative to start of measure 86 | return "#{bars}.#{beats}.#{units}" 87 | end 88 | 89 | end 90 | 91 | end 92 | 93 | -------------------------------------------------------------------------------- /ajm/ruby/ajm_midi2coll.rb: -------------------------------------------------------------------------------- 1 | require 'midilib/sequence' 2 | 3 | VALID_BEAT_UNITS = [1,2,4,8,16] 4 | 5 | inlet_assist 'read/timesig/quantize' 6 | outlet_assist 'note data in coll format', 'track index', 'number of tracks' 7 | 8 | def timesig(beats_per_bar, beat_unit) 9 | beats_per_bar = beats_per_bar.to_i 10 | beat_unit = beat_unit.to_i 11 | valid = true 12 | if beats_per_bar < 1 then 13 | error "Invalid beats per bar '#{beats_per_bar}'. Must be a positive number." 14 | valid = false 15 | end 16 | if not VALID_BEAT_UNITS.include? beat_unit then 17 | error "Invalid beat unit '#{beat_unit}'. " + 18 | "Beat unit must be one of the following: #{VALID_BEAT_UNITS.inspect}" 19 | valid = false 20 | end 21 | if valid 22 | set_local(:beats_per_bar, beats_per_bar) 23 | set_local(:beat_unit, beat_unit) 24 | end 25 | end 26 | 27 | def quantize(ticks) 28 | set_local(:quantize, ticks) 29 | end 30 | 31 | def ccfilter(state=nil) 32 | state = false if not state or state == 0 33 | set_local(:cc_filter, state) 34 | end 35 | 36 | def read(midi_file) 37 | midi_file.strip! 38 | if midi_file =~ /^[^:]{2,}:(.*)/ then midi_file = $1 end # Fixes cross platform issues with drive letters (strip off drive name on OS X) 39 | if File.exists?(midi_file) then 40 | # Settings are stored as local variables to this instance, 41 | # because I am using a shared context for this object to improve performance 42 | # (if not used shared context, these could be globals) 43 | beats_per_bar = get_local(:beats_per_bar) || 4 44 | beat_unit = get_local(:beat_unit) || 4 45 | quantize = get_local(:quantize) 46 | cc_filter = get_local(:cc_filter) 47 | midi2coll = Midi2Coll.new(midi_file, beats_per_bar, beat_unit, quantize, cc_filter) 48 | 49 | num_tracks = midi2coll.number_of_tracks 50 | if num_tracks > 1 51 | has_metadata_track = true 52 | out2 num_tracks-1 # ignore metadata track 53 | else 54 | has_metadata_track = false 55 | out2 1 56 | end 57 | 58 | midi2coll.coll_entries.each_with_index do |entries_for_track, track_index| 59 | next if has_metadata_track and track_index == 0 60 | out1 track_index 61 | out0 'clear' 62 | entries_for_track.each do |note_data| 63 | out0 note_data 64 | end 65 | end 66 | else 67 | error "Cannot find file: #{midi_file}" 68 | end 69 | end 70 | 71 | 72 | class Midi2Coll 73 | 74 | def initialize(midi_file, beats_per_bar=4, beat_unit=4, quantize_in_ticks=nil, cc_filter=false) 75 | @midi_file = midi_file # TODO validate 76 | @track_tick_maps = [] 77 | @beats_per_bar = beats_per_bar.to_i 78 | @quantize = quantize_in_ticks 79 | @cc_filter = cc_filter 80 | 81 | # use midilib to parse the input file 82 | @sequence = MIDI::Sequence.new() 83 | File.open(@midi_file, 'rb') do |file| 84 | @sequence.read(file) 85 | end 86 | @ticks_per_beat = @sequence.ppqn.to_i 87 | 88 | @sequence.tracks.each_with_index do |track,index| 89 | @track_tick_maps[index] = @tick_map = {} 90 | @pitch_map = {} 91 | track.each do |event| 92 | case event 93 | when MIDI::NoteOnEvent 94 | start_note(event) 95 | when MIDI::NoteOffEvent 96 | end_note(event) 97 | when MIDI::Controller 98 | simple_event(event) if not @cc_filter 99 | end 100 | # TODO: support other event types? Pitch Bends? Program changes? Time Signature changes? 101 | end 102 | end 103 | end 104 | 105 | def number_of_tracks 106 | @sequence.tracks.length 107 | end 108 | 109 | def coll_entries 110 | coll_entries = [] 111 | @track_tick_maps.each do |tick_map| 112 | coll_entries_for_track = [] 113 | onset_times = tick_map.keys.sort 114 | onset_times.each do |onset| 115 | events = tick_map[onset] 116 | bbu = ticks_to_bbu(onset) # onset is in ticks 117 | coll_entry = ['store', bbu] # the index for [coll] 118 | 119 | events.each do |event| 120 | case event 121 | when Array 122 | on, off = event 123 | pitch = on.note 124 | velocity = on.velocity 125 | offset = off.time_from_start # in ticks 126 | duration = ticks_to_beats(offset - onset) 127 | coll_entry << "#{pitch} #{velocity} #{duration}" 128 | 129 | when MIDI::Controller 130 | ccval = event.value 131 | ccnum = event.controller 132 | coll_entry << "cc #{ccval} #{ccnum}" 133 | 134 | end 135 | end 136 | coll_entries_for_track << coll_entry 137 | end 138 | coll_entries << coll_entries_for_track 139 | end 140 | return coll_entries 141 | end 142 | 143 | ############################# 144 | private 145 | 146 | def start_note(on_event) 147 | pitch = on_event.note 148 | @pitch_map[pitch] = on_event 149 | end 150 | 151 | def end_note(off_event) 152 | pitch = off_event.note 153 | on_event = @pitch_map[pitch] 154 | if on_event then 155 | note = [on_event, off_event] 156 | add_event(note, on_event.time_from_start) 157 | else 158 | # This will happen if multiple note ons occur at the 159 | # same pitch before the first note off. 160 | # TODO: handle this case by making pitch_map smarter 161 | error "Warning: unmatched note off for pitch #{pitch} at #{ticks_to_bbu(off_time)}" 162 | end 163 | end 164 | 165 | def simple_event(event) 166 | add_event(event, event.time_from_start) 167 | end 168 | 169 | def add_event(event, time_in_ticks) 170 | time_in_ticks = quantize(time_in_ticks) 171 | # Map time to list of events occuring at that time: 172 | events = @tick_map[time_in_ticks] 173 | if events then 174 | events << event 175 | else 176 | @tick_map[time_in_ticks] = [event] 177 | end 178 | end 179 | 180 | def ticks_to_bbu(ticks) 181 | ticks = ticks.to_i 182 | units = ticks % @ticks_per_beat 183 | beats = ticks / @ticks_per_beat # total beat offset 184 | bars = beats / @beats_per_bar + 1 185 | beats = beats % @beats_per_bar + 1 # beats relative to start of measure 186 | return "#{bars}.#{beats}.#{units}" 187 | end 188 | 189 | def ticks_to_beats(ticks, sig_digits_multiplier=1000.0) 190 | beats = ticks/480.0 191 | # limit digits for readability: 192 | beats = (beats*sig_digits_multiplier).round / sig_digits_multiplier if sig_digits_multiplier 193 | return beats 194 | end 195 | 196 | def quantize(ticks) 197 | if @quantize 198 | diff = ticks % @quantize 199 | ticks -= diff 200 | if diff >= @quantize / 2 201 | ticks += @quantize 202 | end 203 | end 204 | return ticks 205 | end 206 | 207 | end 208 | 209 | 210 | -------------------------------------------------------------------------------- /ajm/ruby/ajm_ruby_initialize.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | 3 | def inlet_assist(*params) 4 | $max_object.setInletAssist params.to_java(:string) 5 | end 6 | 7 | def outlet_assist(*params) 8 | $max_object.setOutletAssist params.to_java(:string) 9 | end 10 | 11 | def atom(obj=nil) 12 | if obj 13 | $max_ruby_adapter.toAtoms(obj) 14 | else 15 | com.cycling74.max.Atom.emptyArray 16 | end 17 | end 18 | 19 | # Placeholders for Max hooks: 20 | def bang 21 | 'bang' 22 | end 23 | 24 | def list(*array) 25 | array 26 | end 27 | 28 | def yield_atoms(*params,&block) 29 | params.each do |param| 30 | begin 31 | atoms_or_atom = $max_ruby_adapter.toAtoms(param) 32 | rescue # this compensates for http://jira.codehaus.org/browse/JRUBY-4998 33 | atoms_or_atom = param.to_s 34 | end 35 | if atoms_or_atom.respond_to? :each 36 | atoms_or_atom.each{|atom| yield atom} 37 | else 38 | yield atoms_or_atom 39 | end 40 | end 41 | end 42 | 43 | def puts(*params) 44 | yield_atoms(*params) {|atom| java.lang.System.out.println(atom)} 45 | nil 46 | end 47 | 48 | def print(*params) 49 | yield_atoms(*params) {|atom| java.lang.System.out.print(atom)} 50 | nil 51 | end 52 | 53 | def error(*params) 54 | yield_atoms(*params) {|atom| java.lang.System.err.println(atom)} 55 | nil 56 | end 57 | 58 | def flush 59 | java.lang.System.out.println 60 | nil 61 | end 62 | 63 | def outlet(outlet_index, *params) 64 | if (outlet_index >= $max_object.numOutlets) 65 | error("Invalid outlet index #{outletIdx}") 66 | else 67 | begin 68 | if params.length == 1 69 | # avoid unnecessary nested arrays for things like "outlet 0, [1,2]" 70 | atoms = $max_ruby_adapter.toAtoms(params[0]) 71 | else 72 | atoms = $max_ruby_adapter.toAtoms(params) 73 | end 74 | rescue # this compensates for http://jira.codehaus.org/browse/JRUBY-4998 75 | if params.length == 1 76 | atoms = $max_ruby_adapter.toAtoms(params[0].to_s) 77 | else 78 | atoms = $max_ruby_adapter.toAtoms( params.map{|p| p.to_s} ) 79 | end 80 | end 81 | $max_object.outlet(outlet_index, atoms) 82 | end 83 | nil 84 | end 85 | 86 | def inlet(inlet_index, *params) 87 | params 88 | end 89 | 90 | def inlet_index 91 | $max_object.getInlet 92 | end 93 | 94 | module Kernel 95 | # define_method must be called inside a module or class 96 | (0..9).each do |index| 97 | define_method "out#{index}" do |*params| 98 | outlet(index, *params) 99 | end 100 | define_method "in#{index}" do |*params| 101 | inlet(index, *params) 102 | end 103 | end 104 | end 105 | 106 | def max_object(namespace=nil) 107 | return $max_object if not namespace 108 | names = namespace.split '.' 109 | if(names.length > 1) 110 | context = names[0] 111 | id = names[1] 112 | else 113 | context = $max_object.context 114 | id = names[0] 115 | end 116 | $max_object_map[context][id] 117 | end 118 | 119 | # deprecated, use at_exit 120 | def on_context_destroyed(callback_script) 121 | $max_ruby_adapter.on_context_destroyed(callback_script) 122 | end 123 | 124 | # for use with shared contexts: 125 | LOCAL_STORAGE = {} 126 | def set_local(name,obj) 127 | storage = LOCAL_STORAGE[$max_object] 128 | LOCAL_STORAGE[$max_object] = storage = {} if not storage 129 | storage[name] = obj 130 | end 131 | alias setLocal set_local # for backward compatibility 132 | def get_local(name) 133 | storage = LOCAL_STORAGE[$max_object] 134 | storage[name] if storage 135 | end 136 | alias getLocal get_local # for backward compatibility 137 | def delete_local(name) 138 | storage = LOCAL_STORAGE[$max_object] 139 | storage.delete(name) if storage 140 | end 141 | def has_local?(name) 142 | storage = LOCAL_STORAGE[$max_object] 143 | if storage 144 | storage.has_key?(name) 145 | else 146 | false 147 | end 148 | end 149 | 150 | def set_global(name,obj) 151 | $global_variable_store.set(name.to_s, obj) if name 152 | return obj 153 | end 154 | def get_global(name) 155 | $global_variable_store.get(name.to_s) if name 156 | end 157 | def delete_global(name) 158 | $global_variable_store.delete(name.to_s) if name 159 | end 160 | def has_global?(name) 161 | $global_variable_store.defined(name.to_s) if name 162 | end 163 | 164 | -------------------------------------------------------------------------------- /ajm/ruby/ajmfl_cosy2clip.rb: -------------------------------------------------------------------------------- 1 | $DEBUG=true 2 | require 'cosy' 3 | include Cosy 4 | 5 | OBJECT_NAME = 'ajm.cosy2clip' 6 | TICKS_PER_QUARTER_NOTE = 480 7 | 8 | def render(input) 9 | Cosy::ClipRenderer.new({:input => input}).render 10 | rescue Exception => e 11 | puts e 12 | end 13 | 14 | inlet_assist 'cosy sequence' 15 | outlet_assist 'to live.object','other messages','bang when done','parsed successfully?' 16 | 17 | module Cosy 18 | 19 | class ClipRenderer < AbstractRenderer 20 | 21 | def initialize(options={}) 22 | super 23 | 24 | @clip_duration = 0.0 25 | @notes = [] 26 | @timeline.each_event do |time,event| 27 | case event 28 | when Event::Note 29 | start_time = to_beats(time) 30 | duration = to_beats(event.duration) 31 | end_time = start_time + duration 32 | @clip_duration = end_time if end_time > @clip_duration 33 | @notes << [event.pitch, start_time, duration, event.velocity, 0] 34 | end 35 | end 36 | @clip_duration = @clip_duration.ceil.to_f 37 | end 38 | 39 | def render() 40 | call :select_all_notes 41 | call :replace_selected_notes 42 | call :notes, @notes.length 43 | @notes.each{|note| call :note, *note } 44 | call :done 45 | set :loop_end, @clip_duration 46 | end 47 | 48 | def call(command, *args) 49 | out0 :call, command, *args 50 | end 51 | 52 | def set(param, *args) 53 | out0 :set, param, *args 54 | end 55 | 56 | def to_beats(ticks) 57 | ticks / 480.0 58 | end 59 | 60 | end 61 | 62 | end -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_argv.rb: -------------------------------------------------------------------------------- 1 | return if ARGV[0] == :mute 2 | puts ' ' 3 | puts 'These variables are set:' 4 | puts '$0 = ' + $0.inspect 5 | puts '__FILE__ = ' + __FILE__.inspect 6 | puts '$* = ' + $*.inspect 7 | puts 'ARGV = ' + ARGV.inspect 8 | puts "$LOAD_PATH << #{$LOAD_PATH.last.inspect} # path to the file" 9 | -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_autowatch.rb: -------------------------------------------------------------------------------- 1 | # out0 "uncomment me and save this file" 2 | 3 | -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_call_send.rb: -------------------------------------------------------------------------------- 1 | def my_method(arg0,arg1,arg2) 2 | "arg0=#{arg0}, arg1=#{arg1}, arg2=#{arg2}" 3 | end 4 | 5 | def another_method 6 | "another method's return value" 7 | end 8 | 9 | class MyClass 10 | def method(arg) 11 | %w{the class received value} << arg.inspect 12 | end 13 | end 14 | 15 | $object = MyClass.new -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_file_example.rb: -------------------------------------------------------------------------------- 1 | def custom_method 2 | out0 "Hello from a custom method defined in a file" 3 | end 4 | 5 | def bang 6 | # out0 is a shortcut for: 7 | outlet(0, 'Hello from a custom handler for bang') 8 | end 9 | 10 | def inlet(inlet_index, *args) 11 | out0 'check the Max window' 12 | puts "received a list from inlet #{inlet_index} with #{args.length} items: ", args 13 | end -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_inlet_symbols_to.rb: -------------------------------------------------------------------------------- 1 | def inlet(inlet_index, *params) 2 | out0 params[0].class.to_s+'s:', *params 3 | end -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_inlets.rb: -------------------------------------------------------------------------------- 1 | def in0(*params) 2 | out0 'sum', params.inject(:+) 3 | end 4 | 5 | # use of the splat (*params) is optionally 6 | # it's recommended because it makes the method more flexible 7 | def in2(param1, param2, param3, param4) 8 | params = [param1, param2, param3, param4] 9 | out0 'average', params.inject(:+).to_f/params.length 10 | end 11 | 12 | # when a convenience method (in1 in this case) is not 13 | # defined, it will call the inlet() method 14 | def inlet(inlet_index, *params) 15 | out0 'product', params.inject(:*) 16 | end -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_load_require.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.dirname($0) # put this folder on the LOAD_PATH 2 | -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_scriptfile.rb: -------------------------------------------------------------------------------- 1 | out0 'Hello from ajm_scriptfile.rb' 2 | -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_variable_scope.rb: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # ajm.ruby has separate local variable scopes for each evalution 3 | # If you need to remember a value across separate Max messages, you 4 | # need to use constants, globals, or attributes. 5 | # Note that a constant should only be defined once, and will result 6 | # in an error if you try to assign a different value later. 7 | 8 | Var = 'constants are remembered' 9 | $var = 'globals are remembered' 10 | @var = 'attributes are remembered' 11 | var = 'forgotten' 12 | 13 | def some_method 14 | 'methods are visible' 15 | end 16 | 17 | out0 "local variables are #{var} after a script's evaluation" 18 | -------------------------------------------------------------------------------- /ajm/ruby/help/ajm_version.rb: -------------------------------------------------------------------------------- 1 | out0 JRUBY_VERSION, RUBY_VERSION -------------------------------------------------------------------------------- /ajm/ruby/midilib/info.rb: -------------------------------------------------------------------------------- 1 | module MIDI 2 | 3 | VERSION_MAJOR = 1 4 | VERSION_MINOR = 2 5 | VERSION_TWEAK = 1 6 | Version = "#{VERSION_MAJOR}.#{VERSION_MINOR}.#{VERSION_TWEAK}" 7 | Copyright = 'Copyright (c) 2003-2009 by Jim Menard ' 8 | 9 | end 10 | -------------------------------------------------------------------------------- /ajm/ruby/midilib/io/seqreader.rb: -------------------------------------------------------------------------------- 1 | require 'midilib/io/midifile' 2 | require 'midilib/track' 3 | require 'midilib/event' 4 | 5 | module MIDI 6 | 7 | module IO 8 | 9 | # Reads MIDI files. As a subclass of MIDIFile, this class implements 10 | # the callback methods for each MIDI event and use them to build Track 11 | # and Event objects and give the tracks to a Sequence. 12 | # 13 | # We append new events to the end of a track's event list, bypassing a call 14 | # to Track.#add. This means that we must call Track.recalc_times at the end 15 | # of the track so it can update each event with its time from the track's 16 | # start (see end_track below). 17 | # 18 | # META_TRACK_END events are not added to tracks. This way, we don't have to 19 | # worry about making sure the last event is always a track end event. We 20 | # rely on the SeqWriter to append a META_TRACK_END event to each track when 21 | # it is output. 22 | 23 | class SeqReader < MIDIFile 24 | 25 | # The optional proc block is called once at the start of the file 26 | # and again at the end of each track. There are three arguments 27 | # to the block: the track, the track number (1 through _n_), and 28 | # the total number of tracks. 29 | def initialize(seq, proc = nil) # :yields: track, num_tracks, index 30 | super() 31 | @seq = seq 32 | @track = nil 33 | @chan_mask = 0 34 | @update_block = block_given?() ? Proc.new() : proc 35 | end 36 | 37 | def header(format, ntrks, division) 38 | @seq.format = format 39 | @seq.ppqn = division 40 | 41 | @ntrks = ntrks 42 | @update_block.call(nil, @ntrks, 0) if @update_block 43 | end 44 | 45 | def start_track() 46 | @track = Track.new(@seq) 47 | @seq.tracks << @track 48 | 49 | @pending = [] 50 | end 51 | 52 | def end_track() 53 | # Turn off any pending note on messages 54 | @pending.each { | on | make_note_off(on, 64) } 55 | @pending = nil 56 | 57 | # Don't bother adding the META_TRACK_END event to the track. 58 | # This way, we don't have to worry about making sure the 59 | # last event is always a track end event. 60 | 61 | # Let the track calculate event times from start of track. This is 62 | # in lieu of calling Track.add for each event. 63 | @track.recalc_times() 64 | 65 | # Store bitmask of all channels used into track 66 | @track.channels_used = @chan_mask 67 | 68 | # call update block 69 | @update_block.call(@track, @ntrks, @seq.tracks.length) if @update_block 70 | end 71 | 72 | def note_on(chan, note, vel) 73 | if vel == 0 74 | note_off(chan, note, 64) 75 | return 76 | end 77 | 78 | on = NoteOnEvent.new(chan, note, vel, @curr_ticks) 79 | @track.events << on 80 | @pending << on 81 | track_uses_channel(chan) 82 | end 83 | 84 | def note_off(chan, note, vel) 85 | # Find note on, create note off, connect the two, and remove 86 | # note on from pending list. 87 | @pending.each_with_index { | on, i | 88 | if on.note == note && on.channel == chan 89 | make_note_off(on, vel) 90 | @pending.delete_at(i) 91 | return 92 | end 93 | } 94 | $stderr.puts "note off with no earlier note on (ch #{chan}, note" + 95 | " #{note}, vel #{vel})" if $DEBUG 96 | end 97 | 98 | def make_note_off(on, vel) 99 | off = NoteOffEvent.new(on.channel, on.note, vel, @curr_ticks) 100 | @track.events << off 101 | on.off = off 102 | off.on = on 103 | end 104 | 105 | def pressure(chan, note, press) 106 | @track.events << PolyPressure.new(chan, note, press, @curr_ticks) 107 | track_uses_channel(chan) 108 | end 109 | 110 | def controller(chan, control, value) 111 | @track.events << Controller.new(chan, control, value, @curr_ticks) 112 | track_uses_channel(chan) 113 | end 114 | 115 | def pitch_bend(chan, msb, lsb) 116 | @track.events << PitchBend.new(chan, (msb << 7) + lsb, @curr_ticks) 117 | track_uses_channel(chan) 118 | end 119 | 120 | def program(chan, program) 121 | @track.events << ProgramChange.new(chan, program, @curr_ticks) 122 | track_uses_channel(chan) 123 | end 124 | 125 | def chan_pressure(chan, press) 126 | @track.events << ChannelPressure.new(chan, press, @curr_ticks) 127 | track_uses_channel(chan) 128 | end 129 | 130 | def sysex(msg) 131 | @track.events << SystemExclusive.new(msg, @curr_ticks) 132 | end 133 | 134 | def meta_misc(type, msg) 135 | @track.events << MetaEvent.new(type, msg, @curr_ticks) 136 | end 137 | 138 | # -- 139 | # def sequencer_specific(type, msg) 140 | # end 141 | 142 | # def sequence_number(num) 143 | # end 144 | # ++ 145 | 146 | def text(type, msg) 147 | case type 148 | when META_TEXT, META_LYRIC, META_CUE 149 | @track.events << MetaEvent.new(type, msg, @curr_ticks) 150 | when META_SEQ_NAME, META_COPYRIGHT 151 | @track.events << MetaEvent.new(type, msg, 0) 152 | when META_INSTRUMENT 153 | @track.instrument = msg 154 | when META_MARKER 155 | @track.events << Marker.new(msg, @curr_ticks) 156 | else 157 | $stderr.puts "text = #{msg}, type = #{type}" if $DEBUG 158 | end 159 | end 160 | 161 | # -- 162 | # Don't bother adding the META_TRACK_END event to the track. This way, 163 | # we don't have to worry about always making sure the last event is 164 | # always a track end event. We just have to make sure to write one when 165 | # the track is output back to a file. 166 | # def eot() 167 | # @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks) 168 | # end 169 | # ++ 170 | 171 | def time_signature(numer, denom, clocks, qnotes) 172 | @seq.time_signature(numer, denom, clocks, qnotes) 173 | @track.events << TimeSig.new(numer, denom, clocks, qnotes, @curr_ticks) 174 | end 175 | 176 | # -- 177 | # def smpte(hour, min, sec, frame, fract) 178 | # end 179 | # ++ 180 | 181 | def tempo(microsecs) 182 | @track.events << Tempo.new(microsecs, @curr_ticks) 183 | end 184 | 185 | def key_signature(sharpflat, is_minor) 186 | @track.events << KeySig.new(sharpflat, is_minor, @curr_ticks) 187 | end 188 | 189 | # -- 190 | # def arbitrary(msg) 191 | # end 192 | # ++ 193 | 194 | # Return true if the current track uses the specified channel. 195 | def track_uses_channel(chan) 196 | @chan_mask = @chan_mask | (1 << chan) 197 | end 198 | 199 | end 200 | 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /ajm/ruby/midilib/io/seqwriter.rb: -------------------------------------------------------------------------------- 1 | # Writes MIDI files. 2 | 3 | require 'midilib/event' 4 | require 'midilib/utils' 5 | 6 | module MIDI 7 | 8 | module IO 9 | 10 | class SeqWriter 11 | 12 | def initialize(seq, proc = nil) # :yields: num_tracks, index 13 | @seq = seq 14 | @update_block = block_given?() ? Proc.new() : proc 15 | end 16 | 17 | # Writes a MIDI format 1 file. 18 | def write_to(io) 19 | @io = io 20 | @bytes_written = 0 21 | write_header() 22 | @update_block.call(nil, @seq.tracks.length, 0) if @update_block 23 | @seq.tracks.each_with_index { | track, i | 24 | write_track(track) 25 | @update_block.call(track, @seq.tracks.length, i) if @update_block 26 | } 27 | end 28 | 29 | def write_header 30 | @io.print 'MThd' 31 | write32(6) 32 | write16(1) # Ignore sequence format; write as format 1 33 | write16(@seq.tracks.length) 34 | write16(@seq.ppqn) 35 | end 36 | 37 | def write_track(track) 38 | @io.print 'MTrk' 39 | track_size_file_pos = @io.tell() 40 | write32(0) # Dummy byte count; overwritten later 41 | @bytes_written = 0 # Reset after previous write 42 | 43 | write_instrument(track.instrument) 44 | 45 | prev_event = nil 46 | prev_status = 0 47 | track.events.each { | event | 48 | if !event.kind_of?(Realtime) 49 | write_var_len(event.delta_time) 50 | end 51 | 52 | data = event.data_as_bytes() 53 | status = data[0] # status byte plus channel number, if any 54 | 55 | # running status byte 56 | status = possibly_munge_due_to_running_status_byte(data, 57 | prev_status) 58 | 59 | @bytes_written += write_bytes(data) 60 | 61 | prev_event = event 62 | prev_status = status 63 | } 64 | 65 | # Write track end event. 66 | event = MetaEvent.new(META_TRACK_END) 67 | write_var_len(0) 68 | @bytes_written += write_bytes(event.data_as_bytes()) 69 | 70 | # Go back to beginning of track data and write number of bytes, 71 | # then come back here to end of file. 72 | @io.seek(track_size_file_pos) 73 | write32(@bytes_written) 74 | @io.seek(0, ::IO::SEEK_END) 75 | end 76 | 77 | # If we can use a running status byte, delete the status byte from 78 | # the given data. Return the status to remember for next time as the 79 | # running status byte for this event. 80 | def possibly_munge_due_to_running_status_byte(data, prev_status) 81 | status = data[0] 82 | return status if status >= 0xf0 || prev_status >= 0xf0 83 | 84 | chan = (status & 0x0f) 85 | return status if chan != (prev_status & 0x0f) 86 | 87 | status = (status & 0xf0) 88 | prev_status = (prev_status & 0xf0) 89 | 90 | # Both events are on the same channel. If the two status bytes are 91 | # exactly the same, the rest is trivial. If it's note on/note off, 92 | # we can combine those further. 93 | if status == prev_status 94 | data[0,1] = nil # delete status byte from data 95 | return status + chan 96 | elsif status == NOTE_OFF && data[2] == 64 97 | # If we see a note off and the velocity is 64, we can store 98 | # a note on with a velocity of 0. If the velocity isn't 64 99 | # then storing a note on would be bad because the would be 100 | # changed to 64 when reading the file back in. 101 | data[2] = 0 # set vel to 0; do before possible shrinking 102 | status = NOTE_ON + chan 103 | if prev_status == NOTE_ON 104 | data[0,1] = [] # delete status byte 105 | else 106 | data[0] = status 107 | end 108 | return status 109 | else 110 | # Can't compress data 111 | return status + chan 112 | end 113 | end 114 | 115 | def write_instrument(instrument) 116 | event = MetaEvent.new(META_INSTRUMENT, instrument) 117 | write_var_len(0) 118 | data = event.data_as_bytes() 119 | @bytes_written += write_bytes(data) 120 | end 121 | 122 | def write_var_len(val) 123 | buffer = Utils.as_var_len(val) 124 | @bytes_written += write_bytes(buffer) 125 | end 126 | 127 | def write16(val) 128 | val = (-val | 0x8000) if val < 0 129 | 130 | buffer = [] 131 | @io.putc((val >> 8) & 0xff) 132 | @io.putc(val & 0xff) 133 | @bytes_written += 2 134 | end 135 | 136 | def write32(val) 137 | val = (-val | 0x80000000) if val < 0 138 | 139 | @io.putc((val >> 24) & 0xff) 140 | @io.putc((val >> 16) & 0xff) 141 | @io.putc((val >> 8) & 0xff) 142 | @io.putc(val & 0xff) 143 | @bytes_written += 4 144 | end 145 | 146 | def write_bytes(bytes) 147 | bytes.each { |b| @io.putc(b) } 148 | bytes.length 149 | end 150 | end 151 | 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /ajm/ruby/midilib/measure.rb: -------------------------------------------------------------------------------- 1 | require 'midilib/consts' 2 | 3 | module MIDI 4 | 5 | # The Measure class contains information about a measure from the sequence. 6 | # The measure data is based on the time signature information from the sequence 7 | # and is not stored in the sequence itself 8 | class Measure 9 | # The numerator (top digit) for the measure's time signature 10 | attr_reader :numerator 11 | # The denominator for the measure's time signature 12 | attr_reader :denominator 13 | # Start clock tick for the measure 14 | attr_reader :start 15 | # End clock tick for the measure (inclusive) 16 | attr_reader :end 17 | # The measure number (1-based) 18 | attr_reader :measure_number 19 | # The metronome tick for the measure 20 | attr_reader :metronome_ticks 21 | 22 | # Constructor 23 | def initialize(meas_no, start_time, duration, numer, denom, met_ticks) 24 | @measure_number = meas_no 25 | @start = start_time 26 | @end = start_time + duration - 1 27 | @numerator = numer 28 | @denominator = denom 29 | @metronome_ticks = met_ticks 30 | end 31 | 32 | # Returns a detailed string with information about the measure 33 | def to_s 34 | t = "#{@numerator}/#{2**@denominator}" 35 | m = @metronome_ticks.to_f / 24 36 | "measure #{@measure_number} #{@start}-#{@end} #{t} #{m} qs metronome" 37 | end 38 | 39 | # Returns +true+ if the event is in the measure 40 | def contains_event?(e) 41 | (e.time_from_start >= @start) && (e.time_from_start <= @end) 42 | end 43 | end 44 | 45 | # A specialized container for MIDI::Measure objects, which can be use to map 46 | # event times to measure numbers. Please note that this object has to be remade 47 | # when events are deleted/added in the sequence. 48 | class Measures < Array 49 | # The highest event time in the sequence (at the time when the 50 | # object was created) 51 | attr_reader :max_time 52 | 53 | # The ppqd from the sequence 54 | attr_reader :ppqd 55 | 56 | # Constructor 57 | def initialize(max_time, ppqd) 58 | super(0) 59 | @max_time = max_time 60 | @ppqd = ppqd 61 | end 62 | 63 | # Returns the MIDI::Measure object where the event is located. 64 | # Returns +nil+ if the event isn't found in the container (should 65 | # never happen if the MIDI::Measures object is up to date). 66 | def measure_for_event(e) 67 | detect { | m | m.contains_event?(e) } 68 | end 69 | 70 | # Returns the event's time as a formatted MBT string (Measure:Beat:Ticks) 71 | # as found in MIDI sequencers. 72 | def to_mbt(e) 73 | m = measure_for_event(e) 74 | b = (e.time_from_start.to_f - m.start.to_f) / @ppqd 75 | b *= 24 / m.metronome_ticks 76 | sprintf("%d:%02d:%03d", m.measure_number, b.to_i + 1, (b - b.to_i) * @ppqd) 77 | end 78 | end 79 | 80 | end 81 | -------------------------------------------------------------------------------- /ajm/ruby/midilib/sequence.rb: -------------------------------------------------------------------------------- 1 | require 'midilib/io/seqreader' 2 | require 'midilib/io/seqwriter' 3 | require 'midilib/measure.rb' 4 | 5 | module MIDI 6 | 7 | # A MIDI::Sequence contains MIDI::Track objects. 8 | class Sequence 9 | 10 | include Enumerable 11 | 12 | UNNAMED = 'Unnamed Sequence' 13 | DEFAULT_TEMPO = 120 14 | 15 | NOTE_TO_LENGTH = { 16 | 'whole' => 4.0, 17 | 'half' => 2.0, 18 | 'quarter' => 1.0, 19 | 'eighth' => 0.5, 20 | '8th' => 0.5, 21 | 'sixteenth' => 0.25, 22 | '16th' => 0.25, 23 | 'thirty second' => 0.125, 24 | 'thirtysecond' => 0.125, 25 | '32nd' => 0.125, 26 | 'sixty fourth' => 0.0625, 27 | 'sixtyfourth' => 0.0625, 28 | '64th' => 0.0625 29 | } 30 | 31 | # Array with all tracks for the sequence 32 | attr_accessor :tracks 33 | # Pulses (i.e. clocks) Per Quarter Note resolution for the sequence 34 | attr_accessor :ppqn 35 | # The MIDI file format (0, 1, or 2) 36 | attr_accessor :format 37 | attr_accessor :numer, :denom, :clocks, :qnotes 38 | # The class to use for reading MIDI from a stream. The default is 39 | # MIDI::IO::SeqReader. You can change this at any time. 40 | attr_accessor :reader_class 41 | # The class to use for writeing MIDI from a stream. The default is 42 | # MIDI::IO::SeqWriter. You can change this at any time. 43 | attr_accessor :writer_class 44 | 45 | def initialize 46 | @tracks = Array.new() 47 | @ppqn = 480 48 | 49 | # Time signature 50 | @numer = 4 # Numer + denom = 4/4 time default 51 | @denom = 2 52 | @clocks = 24 # Bug fix Nov 11, 2007 - this is not the same as ppqn! 53 | @qnotes = 8 54 | 55 | @reader_class = IO::SeqReader 56 | @writer_class = IO::SeqWriter 57 | end 58 | 59 | # Sets the time signature. 60 | def time_signature(numer, denom, clocks, qnotes) 61 | @numer = numer 62 | @denom = denom 63 | @clocks = clocks 64 | @qnotes = qnotes 65 | end 66 | 67 | # Returns the song tempo in beats per minute. 68 | def beats_per_minute 69 | return DEFAULT_TEMPO if @tracks.nil? || @tracks.empty? 70 | event = @tracks.first.events.detect { | e | 71 | e.kind_of?(MIDI::Tempo) 72 | } 73 | return event ? (Tempo.mpq_to_bpm(event.tempo)) : DEFAULT_TEMPO 74 | end 75 | alias_method :bpm, :beats_per_minute 76 | alias_method :tempo, :beats_per_minute 77 | 78 | # Given a note length name like "whole", "dotted quarter", or "8th 79 | # triplet", return the length of that note in quarter notes as a delta 80 | # time. 81 | def note_to_delta(name) 82 | return length_to_delta(note_to_length(name)) 83 | end 84 | 85 | # Given a note length name like "whole", "dotted quarter", or "8th 86 | # triplet", return the length of that note in quarter notes as a 87 | # floating-point number, suitable for use as an argument to 88 | # length_to_delta. 89 | # 90 | # Legal names are any value in NOTE_TO_LENGTH, optionally prefixed by 91 | # "dotted_" and/or suffixed by "_triplet". So, for example, 92 | # "dotted_quarter_triplet" returns the length of a dotted quarter-note 93 | # triplet and "32nd" returns 1/32. 94 | def note_to_length(name) 95 | name.strip! 96 | name =~ /^(dotted)?(.*?)(triplet)?$/ 97 | dotted, note_name, triplet = $1, $2, $3 98 | note_name.strip! 99 | mult = 1.0 100 | mult = 1.5 if dotted 101 | mult /= 3.0 if triplet 102 | len = NOTE_TO_LENGTH[note_name] 103 | raise "Sequence.note_to_length: \"#{note_name}\" not understood in \"#{name}\"" unless len 104 | return len * mult 105 | end 106 | 107 | # Translates +length+ (a multiple of a quarter note) into a delta time. 108 | # For example, 1 is a quarter note, 1.0/32.0 is a 32nd note, 1.5 is a 109 | # dotted quarter, etc. Be aware when using division; 1/32 is zero due to 110 | # integer mathematics and rounding. Use floating-point numbers like 1.0 111 | # and 32.0. This method always returns an integer. 112 | # 113 | # See also note_to_delta and note_to_length. 114 | def length_to_delta(length) 115 | return (@ppqn * length).to_i 116 | end 117 | 118 | # Returns the name of the first track (track zero). If there are no 119 | # tracks, returns UNNAMED. 120 | def name 121 | return UNNAMED if @tracks.empty? 122 | return @tracks.first.name() 123 | end 124 | 125 | # Hands the name to the first track. Does nothing if there are no tracks. 126 | def name=(name) 127 | return if @tracks.empty? 128 | @tracks.first.name = name 129 | end 130 | 131 | # Reads a MIDI stream. 132 | def read(io, proc = nil) # :yields: track, num_tracks, index 133 | reader = @reader_class.new(self, block_given?() ? Proc.new() : proc) 134 | reader.read_from(io) 135 | end 136 | 137 | # Writes to a MIDI stream. 138 | def write(io, proc = nil) # :yields: track, num_tracks, index 139 | writer = @writer_class.new(self, block_given?() ? Proc.new() : proc) 140 | writer.write_to(io) 141 | end 142 | 143 | # Iterates over the tracks. 144 | def each # :yields: track 145 | @tracks.each { | track | yield track } 146 | end 147 | 148 | # Returns a Measures object, which is an array container for all measures 149 | # in the sequence 150 | def get_measures 151 | # Collect time sig events and scan for last event time 152 | time_sigs = [] 153 | max_pos = 0 154 | @tracks.each { |t| 155 | t.each { |e| 156 | time_sigs << e if e.timesig? 157 | max_pos = e.time_from_start if e.time_from_start > max_pos 158 | } 159 | } 160 | time_sigs.sort { |x,y| x.time_from_start <=> y.time_from_start } 161 | 162 | # Add a "fake" time sig event at the very last position of the sequence, 163 | # just to make sure the whole sequence is calculated. 164 | t = MIDI::TimeSig.new(4, 2, 24, 8, 0) 165 | t.time_from_start = max_pos 166 | time_sigs << t 167 | 168 | # Default to 4/4 169 | measure_length = @ppqn * 4 170 | oldnumer, olddenom, oldbeats = 4, 2, 24 171 | 172 | measures = MIDI::Measures.new(max_pos, @ppqn) 173 | curr_pos = 0 174 | curr_meas_no = 1 175 | time_sigs.each { |te| 176 | meas_count = (te.time_from_start - curr_pos) / measure_length 177 | meas_count += 1 if (te.time_from_start - curr_pos) % measure_length > 0 178 | 1.upto(meas_count) { |i| 179 | measures << MIDI::Measure.new(curr_meas_no, 180 | curr_pos, measure_length, oldnumer, olddenom, oldbeats) 181 | curr_meas_no += 1 182 | curr_pos += measure_length 183 | } 184 | oldnumer, olddenom, oldbeats = te.numerator, te.denominator, te.metronome_ticks 185 | measure_length = te.measure_duration(@ppqn) 186 | } 187 | measures 188 | end 189 | 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /ajm/ruby/midilib/track.rb: -------------------------------------------------------------------------------- 1 | require 'midilib/event' 2 | 3 | module MIDI 4 | 5 | # A Track is a list of events. 6 | # 7 | # When you modify the +events+ array, make sure to call recalc_times so 8 | # each Event gets its +time_from_start+ recalculated. 9 | # 10 | # A Track also holds a bitmask that specifies the channels used by the track. 11 | # This bitmask is set when the track is read from the MIDI file by an 12 | # IO::SeqReader but is _not_ kept up to date by any other methods. 13 | 14 | class Track 15 | 16 | include Enumerable 17 | 18 | UNNAMED = 'Unnamed' 19 | 20 | attr_accessor :events, :channels_used 21 | attr_reader :sequence 22 | 23 | def initialize(sequence) 24 | @sequence = sequence 25 | @events = Array.new() 26 | 27 | # Bitmask of all channels used. Set when track is read in from 28 | # a MIDI file. 29 | @channels_used = 0 30 | end 31 | 32 | # Return track name. If there is no name, return UNNAMED. 33 | def name 34 | event = @events.detect { | e | 35 | e.meta? && e.meta_type == META_SEQ_NAME 36 | } 37 | return event ? event.data_as_str : UNNAMED 38 | end 39 | 40 | # Set track name. Replaces or creates a name meta-event. 41 | def name=(name) 42 | event = @events.detect { | e | 43 | e.meta? && e.meta_type == META_SEQ_NAME 44 | } 45 | if event 46 | event.data = name 47 | else 48 | event = MetaEvent.new(META_SEQ_NAME, name, 0) 49 | @events[0, 0] = event 50 | end 51 | end 52 | 53 | def instrument 54 | MetaEvent.bytes_as_str(@instrument) 55 | end 56 | 57 | def instrument=(str_or_bytes) 58 | @instrument = case str_or_bytes 59 | when String 60 | MetaEvent.str_as_bytes(str_or_bytes) 61 | else 62 | str_or_bytes 63 | end 64 | end 65 | 66 | # Merges an array of events into our event list. After merging, the 67 | # events' time_from_start values are correct so you don't need to worry 68 | # about calling recalc_times. 69 | def merge(event_list) 70 | @events = merge_event_lists(@events, event_list) 71 | end 72 | 73 | # Merges two event arrays together. Does not modify this track. 74 | def merge_event_lists(list1, list2) 75 | recalc_times(0, list1) 76 | recalc_times(0, list2) 77 | list = (list1 + list2).sort_by { | e | e.time_from_start } 78 | recalc_delta_from_times(0, list) 79 | return list 80 | end 81 | 82 | # Quantize every event. length_or_note is either a length (1 = quarter, 83 | # 0.25 = sixteenth, 4 = whole note) or a note name ("sixteenth", "32nd", 84 | # "8th triplet", "dotted quarter"). 85 | # 86 | # Since each event's time_from_start is modified, we call 87 | # recalc_delta_from_times after each event quantizes itself. 88 | def quantize(length_or_note) 89 | delta = case length_or_note 90 | when String 91 | @sequence.note_to_delta(length_or_note) 92 | else 93 | @sequence.length_to_delta(length_or_note.to_i) 94 | end 95 | @events.each { | event | event.quantize_to(delta) } 96 | recalc_delta_from_times 97 | end 98 | 99 | # Recalculate start times for all events in +list+ from starting_at to 100 | # end. 101 | def recalc_times(starting_at=0, list=@events) 102 | t = (starting_at == 0) ? 0 : list[starting_at - 1].time_from_start 103 | list[starting_at .. -1].each { | e | 104 | t += e.delta_time 105 | e.time_from_start = t 106 | } 107 | end 108 | 109 | # The opposite of recalc_times: recalculates delta_time for each event 110 | # from each event's time_from_start. This is useful, for example, when 111 | # merging two event lists. As a side-effect, elements from starting_at 112 | # are sorted by time_from_start. 113 | def recalc_delta_from_times(starting_at=0, list=@events) 114 | prev_time_from_start = 0 115 | # We need to sort the sublist. sublist.sort! does not do what we want. 116 | list[starting_at .. -1] = list[starting_at .. -1].sort { | e1, e2 | 117 | e1.time_from_start <=> e2.time_from_start 118 | } 119 | list[starting_at .. -1].each { | e | 120 | e.delta_time = e.time_from_start - prev_time_from_start 121 | prev_time_from_start = e.time_from_start 122 | } 123 | end 124 | 125 | # Iterate over events. 126 | def each # :yields: event 127 | @events.each { | event | yield event } 128 | end 129 | 130 | # Sort events by their time_from_start. After sorting, 131 | # recalc_delta_from_times is called to make sure that the delta times 132 | # reflect the possibly new event order. 133 | def sort 134 | @events = @events.sort_by { | e | e.time_from_start } 135 | recalc_delta_from_times() 136 | end 137 | end 138 | 139 | end 140 | -------------------------------------------------------------------------------- /ajm/ruby/midilib/utils.rb: -------------------------------------------------------------------------------- 1 | module MIDI 2 | 3 | # Utility methods. 4 | class Utils 5 | 6 | # MIDI note names. NOTE_NAMES[0] is 'C', NOTE_NAMES[1] is 'C#', etc. 7 | NOTE_NAMES = [ 8 | 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B' 9 | ] 10 | 11 | # Given a MIDI note number, return the name and octave as a string. 12 | def Utils.note_to_s(num) 13 | note = num % 12 14 | octave = num / 12 15 | return "#{NOTE_NAMES[note]}#{octave - 1}" 16 | end 17 | 18 | # Given an integer, returns it as a variable length array of bytes (the 19 | # format used by MIDI files). 20 | # 21 | # The converse operation--converting a var len into a number--requires 22 | # input from a stream of bytes. Therefore we don't supply it here. That is 23 | # a part of the MIDIFile class. 24 | def Utils.as_var_len(val) 25 | buffer = [] 26 | buffer << (val & 0x7f) 27 | val = (val >> 7) 28 | while val > 0 29 | buffer << (0x80 + (val & 0x7f)) 30 | val = (val >> 7) 31 | end 32 | return buffer.reverse! 33 | end 34 | 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /ajm/ruby/webserver/ajm_servlets.rb: -------------------------------------------------------------------------------- 1 | class OutputToMax < WEBrick::HTTPServlet::AbstractServlet 2 | def initialize(server, root) 3 | super 4 | @root = root 5 | end 6 | def do_GET(request, response) 7 | request.query.to_a.each do |pair| 8 | out0 pair 9 | end 10 | response['Content-Type'] = 'text/html' 11 | response.body = open "#{@root}/web/to_max.html" do |file| 12 | file.read 13 | end 14 | end 15 | alias do_POST do_GET 16 | end 17 | 18 | class SourceViewer < WEBrick::HTTPServlet::AbstractServlet 19 | def initialize(server, root) 20 | super 21 | @root = root 22 | end 23 | def do_GET(request, response) 24 | response['Content-Type'] = 'text/plain' 25 | response.body = open "#{@root}#{request.path_info}" do |file| 26 | file.read 27 | end 28 | end 29 | alias do_POST do_GET 30 | end -------------------------------------------------------------------------------- /ajm/ruby/webserver/ajm_webserver.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | require 'webrick' 3 | require 'erb' 4 | 5 | class AjmWebServer 6 | 7 | @@instances = [] 8 | 9 | def initialize(config = {}) 10 | @server = WEBrick::HTTPServer.new(config) 11 | @server.config[:MimeTypes]['rhtml'] = 'text/html' 12 | @@instances << self 13 | end 14 | 15 | def port 16 | @server.config[:Port] 17 | end 18 | 19 | def root 20 | @server.config[:DocumentRoot] 21 | end 22 | 23 | def servlet(*args) 24 | @server.mount(*args) 25 | end 26 | 27 | def start 28 | Thread.new { 29 | begin 30 | puts "Starting server on port #{port}" 31 | puts "Serving files from #{root}" 32 | @server.start 33 | rescue 34 | error $! 35 | end 36 | } 37 | end 38 | 39 | def stop 40 | puts "Shutting down server on port #{port} ..." 41 | @server.shutdown 42 | while @server.status != :Stop do 43 | puts '...' 44 | java.lang.Thread.sleep(500) 45 | end 46 | puts "Server on port #{port} stopped\n " 47 | end 48 | 49 | def self.stop_all 50 | @@instances.each do |instance| 51 | instance.stop 52 | end 53 | end 54 | 55 | end 56 | 57 | at_exit { AjmWebServer.stop_all } 58 | -------------------------------------------------------------------------------- /ajm/ruby/webserver/ajm_webserver_example.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.dirname($0) # put this folder on the LOAD_PATH 2 | require 'ajm_webserver' 3 | require 'ajm_servlets' 4 | require 'pathname' 5 | 6 | ########################################################### 7 | # set root to the folder containing this script file 8 | this_file = Pathname.new($0) 9 | WEB_ROOT = this_file.parent 10 | # You might normally want to use a subdirectory: 11 | # WEB_ROOT = this_file.parent + Pathname.new('webroot') 12 | # or set absolute path: 13 | # WEB_ROOT = '/Users/user/some/path' 14 | 15 | PORT = if ARGV[0] then ARGV[0] else 9000 end 16 | 17 | ########################################################### 18 | # All you need to set is a port and directory. 19 | # See WEBrick documentation for more config options. 20 | server = AjmWebServer.new({ 21 | :Port => PORT, 22 | :DocumentRoot => WEB_ROOT, 23 | :Logger => WEBrick::Log.new($stdout, WEBrick::Log::INFO), 24 | :AccessLog => [] 25 | }) 26 | 27 | 28 | ########################################################### 29 | # Some example servlets showing custom request handling 30 | server.servlet '/to_max', OutputToMax, WEB_ROOT 31 | server.servlet '/source', SourceViewer, WEB_ROOT 32 | 33 | ########################################################### 34 | # The most important part! 35 | # Needs to happen after the servlets are registered 36 | server.start 37 | 38 | 39 | ########################################################### 40 | # Expose methods to Max to set attributes that can be used 41 | # in .rhtml files: 42 | 43 | $ATTR = {} 44 | def setattr(name,val=nil) 45 | if(val) 46 | $ATTR[name] = val 47 | else 48 | $ATTR.delete(name) 49 | end 50 | end 51 | 52 | def getattr(name) 53 | $ATTR[name] 54 | end 55 | 56 | # convenience method to set attrs with minimal syntax: 57 | def list(args) 58 | if args.length > 2 59 | $ATTR[args[0]] = args[1..-1] 60 | elsif args.length == 2 61 | $ATTR[args[0]] = args[1] 62 | elsif args.length == 1 63 | $ATTR.delete(args[0]) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /ajm/ruby/webserver/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/ajm/ruby/webserver/favicon.ico -------------------------------------------------------------------------------- /ajm/ruby/webserver/filebrowse/file.html: -------------------------------------------------------------------------------- 1 |
Some HTML
2 | -------------------------------------------------------------------------------- /ajm/ruby/webserver/filebrowse/file.txt: -------------------------------------------------------------------------------- 1 | P 2 | L 3 | A 4 | I 5 | N 6 | T 7 | E 8 | X 9 | T 10 | -------------------------------------------------------------------------------- /ajm/ruby/webserver/index.html: -------------------------------------------------------------------------------- 1 | 2 | ajm.ruby 3 | 4 |

5 | ajm.ruby web server example 6 |

7 |

8 | This page is being served from ajm.ruby via 9 | WEBrick. 10 |

11 |

12 | Webserver Examples: 13 |

18 |

19 |

20 | View the source for: 21 |

    22 |
  • The code that runs this webserver
  • 23 |
  • The Max webserver class
  • 24 |
  • Custom servlets for source code viewing and sending messages to Max
  • 25 |

    26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ajm/ruby/webserver/web/erb.rhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    4 | This table shows attributes set from inside Max. 5 | Reload this page to see changes made in Max. 6 | 7 | 8 | 9 | 10 | 11 | <% $ATTR.to_a.each do |pair| %> 12 | 13 | 14 | 15 | 16 | <% end %> 17 |
    NameValue
    <%= pair[0] %><%= pair[1].inspect %>
    18 |

    19 |

    20 | 24 | view source for this page 25 |

    26 |

    Home

    27 | 28 | -------------------------------------------------------------------------------- /ajm/ruby/webserver/web/to_max.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | input1:
    5 | input2:
    6 | 7 |
    8 |

    9 | 10 | view source for this page 11 | 12 |

    13 |

    Home

    14 | 15 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | 81 | 82 | 83 | 84 | Test]]> 85 | 86 | Copyright © 2007-2010 Adam Murray. All Rights Reserved.]]> 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /lib/ant-junit.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/lib/ant-junit.jar -------------------------------------------------------------------------------- /lib/bsf.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/lib/bsf.jar -------------------------------------------------------------------------------- /lib/jruby.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/lib/jruby.jar -------------------------------------------------------------------------------- /lib/junit-4.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/lib/junit-4.5.jar -------------------------------------------------------------------------------- /lib/max.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/ajm_objects/2a002a6a88f589ec89713b7a0f18bebbb08a05dd/lib/max.jar -------------------------------------------------------------------------------- /license/LICENSE-ajm-objects.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011, Adam Murray (adam@compusition.com). 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use of ajm objects in source and binary forms, with or 6 | without modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in 14 | the documentation and/or other materials provided with the 15 | distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 21 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /license/LICENSE-bsf.txt: -------------------------------------------------------------------------------- 1 | The Apache Software License, Version 1.1 2 | 3 | Copyright (c) 2002 The Apache Software Foundation. All rights 4 | reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | 3. The end-user documentation included with the redistribution, 19 | if any, must include the following acknowledgment: 20 | "This product includes software developed by the 21 | Apache Software Foundation (http://www.apache.org/)." 22 | Alternately, this acknowledgment may appear in the software itself, 23 | if and wherever such third-party acknowledgments normally appear. 24 | 25 | 4. The names "BSF", "Apache", and "Apache Software Foundation" must 26 | not be used to endorse or promote products derived from this 27 | software without prior written permission. For written 28 | permission, please contact apache@apache.org. 29 | 30 | 5. Products derived from this software may not be called "Apache", 31 | nor may "Apache" appear in their name, without prior written 32 | permission of the Apache Software Foundation. 33 | 34 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 35 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 36 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37 | DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 38 | ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 39 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 40 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 41 | USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 42 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 43 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 44 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 45 | SUCH DAMAGE. 46 | 47 | This software consists of voluntary contributions made by many individuals 48 | on behalf of the Apache Software Foundation and was originally created by 49 | Sanjiva Weerawarana and others at International Business Machines 50 | Corporation. For more information on the Apache Software Foundation, 51 | please see . 52 | 53 | 54 | -------------------------------------------------------------------------------- /license/LICENSE-midilib.txt: -------------------------------------------------------------------------------- 1 | Author: Jim Menard (jimm@io.com) 2 | Copyright: Copyright (c) 2003-2005 Jim Menard 3 | License: Distributed under the same license as Ruby. 4 | 5 | midilib is copyrighted free software by Jim Menard and is released under the same license as Ruby. 6 | See the Ruby license at www.ruby-lang.org/en/LICENSE.txt. 7 | -------------------------------------------------------------------------------- /license/jruby/COPYING: -------------------------------------------------------------------------------- 1 | JRuby is released under a tri CPL/GPL/LGPL license. You can use it, 2 | redistribute it and/or modify it under the terms of the: 3 | 4 | CPL - see COPYING.CPL file 5 | GPL - see COPYING.GPL file 6 | LGPL - see COPYING.LGPL file 7 | 8 | Some additional libraries distributed with JRuby are not covered by 9 | JRuby's licence. See the licence files for the respective libraries in 10 | the 'lib' directory for more information and also LICENSE.RUBY for most 11 | files found in src/lib/ruby/1.8. 12 | -------------------------------------------------------------------------------- /src/ajm/code.java: -------------------------------------------------------------------------------- 1 | package ajm; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.awt.BorderLayout; 31 | import java.awt.Dimension; 32 | import java.awt.Font; 33 | import java.awt.event.ActionEvent; 34 | import java.util.ArrayList; 35 | import java.util.List; 36 | 37 | import javax.swing.AbstractAction; 38 | import javax.swing.JButton; 39 | import javax.swing.JComponent; 40 | import javax.swing.JFrame; 41 | import javax.swing.JPanel; 42 | import javax.swing.JScrollPane; 43 | import javax.swing.JTabbedPane; 44 | import javax.swing.JTextArea; 45 | import javax.swing.SwingUtilities; 46 | 47 | import ajm.maxsupport.AbstractMaxObject; 48 | import ajm.util.TextBlock; 49 | import ajm.util.Utils; 50 | 51 | import com.cycling74.max.Atom; 52 | 53 | /** 54 | * Multi-tab plaintext editor. 55 | * 56 | * @author Adam Murray (adam@compusition.com) 57 | */ 58 | public class code extends AbstractMaxObject { 59 | 60 | // TODO: remember the current index in embedMessage 61 | 62 | // TODO: after clicking a tab, put focus on the text area. 63 | // tried it doesn't work, maybe TextArea needs to be made focusable? 64 | // try focusing on something else and see if it's just broken in max 65 | 66 | private static int NUM_TABS = 6; 67 | 68 | private JFrame frame; 69 | private JTabbedPane tabs; 70 | private List textAreas = new ArrayList(); 71 | // private String initText = ""; 72 | private RunAction run = new RunAction(this); 73 | 74 | public code(Atom[] args) { 75 | 76 | declareIO(1, 2); 77 | 78 | if (args.length > 0 && args[0].isInt()) { 79 | NUM_TABS = args[0].getInt(); 80 | } 81 | 82 | frame = new JFrame("Editor"); 83 | frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); 84 | 85 | tabs = new JTabbedPane(); 86 | /* Doesn't seem to work. Is it a Max thing? 87 | tabs.addChangeListener(new ChangeListener() { 88 | public void stateChanged(ChangeEvent e) { 89 | System.out.println(tabs.getSelectedIndex()); 90 | activeTextArea().requestFocus(); 91 | } 92 | }); 93 | */ 94 | for (int i = 0; i < NUM_TABS; i++) { 95 | tabs.add(i + "", createEditor()); 96 | } 97 | frame.add(tabs, BorderLayout.CENTER); 98 | 99 | JPanel buttonPanel = new JPanel(); 100 | buttonPanel.add(new JButton(run)); 101 | frame.add(buttonPanel, BorderLayout.PAGE_END); 102 | 103 | SwingUtilities.invokeLater(new Runnable() { 104 | public void run() { 105 | frame.pack(); 106 | // frame.setVisible(true); 107 | } 108 | }); 109 | } 110 | 111 | @Override 112 | protected void notifyDeleted() { 113 | /* 114 | SwingUtilities.invokeLater(new Runnable() { 115 | public void run() { 116 | frame.dispose(); 117 | } 118 | }); 119 | */ 120 | super.notifyDeleted(); 121 | }; 122 | 123 | private final String OBJ_ID = hashCode() + ""; 124 | 125 | private String getTextBlockName(int tabIndex) { 126 | return OBJ_ID + "::tab." + tabIndex; 127 | } 128 | 129 | public void dblclick() { 130 | open(); 131 | } 132 | 133 | public void open() { 134 | // if (frame != null) { 135 | frame.setVisible(true); 136 | // } 137 | } 138 | 139 | public void set(Atom[] args) { 140 | if (args.length > 1 && args[0].isInt()) { 141 | int index = args[0].getInt(); 142 | if (index > 0 && index < textAreas.size()) { 143 | textAreas.get(index).setText(Utils.detokenize(Atom.removeFirst(args))); 144 | return; 145 | } 146 | } 147 | activeTextArea().setText(Utils.detokenize(args)); 148 | 149 | } 150 | 151 | public void run(Atom[] args) { 152 | int index = tabs.getSelectedIndex(); 153 | if (args.length > 0 && args[0].isInt()) { 154 | int i = args[0].getInt(); 155 | if (i > 0 && i < textAreas.size()) { 156 | index = i; 157 | } 158 | } 159 | output(index); 160 | } 161 | 162 | public void bang() { 163 | output(tabs.getSelectedIndex()); 164 | } 165 | 166 | private void output(int tabIndex) { 167 | String textblock = getTextBlockName(tabIndex); 168 | TextBlock.set(textblock, textAreas.get(tabIndex).getText()); 169 | outlet(1, tabIndex); 170 | outlet(0, "textblock", getTextBlockName(tabIndex)); 171 | } 172 | 173 | private JTextArea activeTextArea() { 174 | return textAreas.get(tabs.getSelectedIndex()); 175 | } 176 | 177 | private String getText(int tabIndex) { 178 | return textAreas.get(tabIndex).getText(); 179 | } 180 | 181 | public void save() { 182 | Atom[] contents = new Atom[NUM_TABS]; 183 | for (int i = 0; i < NUM_TABS; i++) { 184 | String text = getText(i); 185 | // This is completely broken. Max pretty much makes it impossible for me to embed 186 | // arbitrary strings. Maybe it will work in Max 5... 187 | System.out.println("TEXT = " + text); 188 | // text = text.replaceAll("\"", "\\\\\""); 189 | // text = text.replaceAll(" ", "\\\\ "); 190 | System.out.println("Escaped text = " + text); 191 | contents[i] = Atom.newAtom(text); 192 | System.out.println(contents[i].toString()); 193 | } 194 | embedMessage("settabs", contents); 195 | } 196 | 197 | public void settabs(Atom[] args) { 198 | for (int i = 0; i < args.length && i < NUM_TABS; i++) { 199 | System.out.println("Setting text: " + args[i].toString()); 200 | textAreas.get(i).setText(args[i].toString()); 201 | } 202 | } 203 | 204 | private JComponent createEditor() { 205 | JTextArea textArea = new JTextArea(); 206 | textArea.setTabSize(2); // this probably isn't appropriate for other languages 207 | textArea.setFont(new Font("Monospaced", Font.PLAIN, 11)); 208 | // textArea.setText(initText); 209 | textAreas.add(textArea); 210 | 211 | JScrollPane scroller = new JScrollPane(textArea); 212 | scroller.setPreferredSize(new Dimension(300, 400)); 213 | return scroller; 214 | } 215 | 216 | @SuppressWarnings("serial") 217 | private static class RunAction extends AbstractAction { 218 | private code thisObj; 219 | 220 | public RunAction(code obj) { 221 | super("Run"); 222 | putValue(SHORT_DESCRIPTION, "Send the code out this object's outlet"); 223 | thisObj = obj; 224 | } 225 | 226 | public void actionPerformed(ActionEvent e) { 227 | thisObj.bang(); 228 | } 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /src/ajm/eval.java: -------------------------------------------------------------------------------- 1 | package ajm; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.List; 31 | 32 | import ajm.maxsupport.AbstractMaxRubyObject; 33 | import ajm.seqsupport.Item; 34 | import ajm.seqsupport.Parser; 35 | import ajm.util.Utils; 36 | 37 | import com.cycling74.max.Atom; 38 | import com.cycling74.max.Executable; 39 | 40 | /** 41 | * The ajm.eval MaxObject 42 | * 43 | * @author Adam Murray (adam@compusition.com) 44 | */ 45 | public class eval extends AbstractMaxRubyObject { 46 | 47 | Parser parser = new Parser(); 48 | 49 | public eval(Atom[] args) { 50 | declareIO(1, 1); 51 | setInletAssist(new String[] { "message" }); 52 | setOutletAssist(new String[] { "evaluated message" }); 53 | // TODO parsing options 54 | } 55 | 56 | @Override 57 | protected Executable getInitializer() { 58 | return new EvalInitializer(); 59 | } 60 | 61 | protected class EvalInitializer extends DefaultRubyInitializer { 62 | @Override 63 | public void execute() { 64 | super.execute(); 65 | parser.setRubyEvaluator(ruby); 66 | } 67 | } 68 | 69 | public void list(Atom[] list) { 70 | anything(null, list); 71 | } 72 | 73 | public void anything(String msg, Atom[] args) { 74 | try { 75 | List items = parser.parse(msg, args); 76 | Atom[] atoms = new Atom[items.size()]; 77 | for (int i = 0; i < atoms.length; i++) { 78 | Item item = items.get(i); 79 | item.getValue(); // evaluate ruby, if needed 80 | atoms[i] = item.toAtom(); 81 | } 82 | outlet(0, atoms); 83 | } 84 | catch (Exception e) { 85 | err("Could not evaluate: " + Utils.detokenize(msg, args) + "\n" + e.getMessage()); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/ajm/maxsupport/AbstractMaxObject.java: -------------------------------------------------------------------------------- 1 | package ajm.maxsupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.io.PrintStream; 31 | 32 | import ajm.util.Logger; 33 | 34 | import com.cycling74.max.Executable; 35 | import com.cycling74.max.MaxObject; 36 | import com.cycling74.max.MaxQelem; 37 | 38 | /** 39 | * Common behavior for ajm objects. 40 | * 41 | * @author Adam Murray (adam@compusition.com) 42 | */ 43 | public abstract class AbstractMaxObject extends MaxObject implements Logger { 44 | 45 | protected boolean verbose = false; 46 | 47 | protected boolean initialized = false; 48 | protected MaxQelem initializer = getInitializerQelem(); 49 | 50 | // The initializer should never be null inside Max, but needs to be null in unit tests 51 | 52 | public AbstractMaxObject() { 53 | declareAttribute("verbose"); 54 | if (initializer == null) { 55 | initialized = true; 56 | } 57 | else { 58 | initializer.set(); 59 | } 60 | } 61 | 62 | private final MaxQelem getInitializerQelem() { 63 | Executable initializer = getInitializer(); 64 | return initializer == null ? null : new MaxQelem(initializer); 65 | } 66 | 67 | /** 68 | * A subclass can override this method to return a MaxQelem that can do operations not normally available in the 69 | * constructor, such as outputting messages. See discussion at 70 | * http://www.cycling74.com/forums/index.php?t=rview&goto=114649 Note that if this method is overriden, it is the 71 | * responsibility of the initializer code to set initialized = true. 72 | * 73 | * @return an Executable that executes initialization code. Must not be null. 74 | */ 75 | protected Executable getInitializer() { 76 | return new DefaultInitializer(); 77 | } 78 | 79 | protected class DefaultInitializer implements Executable { 80 | public void execute() { 81 | initialized = true; 82 | } 83 | } 84 | 85 | @Override 86 | protected void notifyDeleted() { 87 | if (initializer != null) { 88 | initializer.release(); 89 | } 90 | } 91 | 92 | // for use with debugging unit tests, must be set from the test after instantiation 93 | private PrintStream debugOut; 94 | 95 | /** 96 | * Unit tests can set this output stream back to system.out (or a log file) for testing purposes outside of the Max 97 | * environment. Normally any MaxObject will "steal" System.out and set it to the Max output stream (which is not 98 | * actually available outside of Max). 99 | * 100 | * @param debugOut 101 | */ 102 | protected void setDebugOut(PrintStream debugOut) { 103 | this.debugOut = debugOut; 104 | } 105 | 106 | public void debug(String message) { 107 | if (debugOut != null) { 108 | debugOut.println(message); 109 | } 110 | } 111 | 112 | /** 113 | * Automatically prepends the object name to the beginning of error messages. 114 | * 115 | * @param message 116 | * the error message 117 | */ 118 | public void info(String message) { 119 | info(message, false); 120 | } 121 | public void info(String message, boolean force) { 122 | if (verbose || force) { 123 | post(this.getClass().getName() + ": " + message); 124 | } 125 | } 126 | 127 | /** 128 | * Automatically prepends the object name to the beginning of error messages. 129 | * 130 | * @param message 131 | * the error message 132 | */ 133 | public void err(String message) { 134 | error(this.getClass().getName() + ": " + message); 135 | } 136 | 137 | public String toString() { 138 | return getClass().getName() + "#<" + Integer.toHexString(hashCode()) + ">"; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/ajm/maxsupport/AbstractMaxRubyObject.java: -------------------------------------------------------------------------------- 1 | package ajm.maxsupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import org.jruby.CompatVersion; 31 | 32 | import com.cycling74.max.*; 33 | 34 | import ajm.rubysupport.*; 35 | import ajm.util.Utils; 36 | 37 | /** 38 | * Superclass for objects that support Ruby scripting. 39 | * 40 | * @author Adam Murray (adam@compusition.com) 41 | */ 42 | public abstract class AbstractMaxRubyObject extends AbstractMaxObject { 43 | 44 | protected String context = null; 45 | protected String id = defaultId(); 46 | private boolean autoinit = false; 47 | 48 | protected Atom[] rubyVersionValue; 49 | protected CompatVersion rubyVersion; 50 | 51 | protected MaxRubyAdapter ruby; 52 | 53 | protected AbstractMaxRubyObject self = this; 54 | 55 | public AbstractMaxRubyObject() { 56 | super(); 57 | declareAttribute("context", "getcontext", "context"); 58 | declareAttribute("id", "getid", "id"); 59 | declareAttribute("autoinit"); 60 | declareAttribute("ruby_version", "getruby_version", "ruby_version"); 61 | } 62 | 63 | @Override 64 | protected Executable getInitializer() { 65 | return new DefaultRubyInitializer(); 66 | } 67 | 68 | protected class DefaultRubyInitializer extends DefaultInitializer { 69 | @Override 70 | public void execute() { 71 | super.execute(); 72 | try { 73 | ruby = new MaxRubyAdapter(self, context, id, rubyVersion); 74 | } 75 | catch (IdInUseException e) { 76 | String availableId = e.getMessage(); 77 | error("id " + id + " not available. Using: " + availableId); 78 | id = availableId; 79 | ruby = new MaxRubyAdapter(self, context, id, rubyVersion); 80 | } 81 | if (autoinit) { 82 | ruby.init(); 83 | /* Doing this at construction time causes Max to hang for a while if there are many instances of this object. 84 | Thus autoinit is false by default. 85 | The downside to not init'ing here is there will be a slight delay the first time a script tries to evaluate 86 | The hang delay got much shorter with JRuby 1.1. */ 87 | } 88 | } 89 | } 90 | 91 | public Atom[] getcontext() { 92 | return Atom.newAtom(new String[]{context}); 93 | } 94 | 95 | public String context() { 96 | return context; 97 | } 98 | 99 | public void context(Atom[] params) { 100 | String context = Utils.toString(params); 101 | this.context = context; 102 | if (ruby != null) { 103 | ruby.setContext(context); 104 | } // else we're still initializing and the initalizer should handle this 105 | } 106 | 107 | public Atom[] getid() { 108 | return Atom.newAtom(new String[]{id}); 109 | } 110 | 111 | public String id() { 112 | return id; 113 | } 114 | 115 | public void id(Atom[] params) { 116 | String id = Utils.toString(params); 117 | if (id == null || "".equals(id)) { 118 | id = defaultId(); 119 | } 120 | this.id = id; 121 | if (ruby != null) { 122 | try { 123 | ruby.setId(id); 124 | } 125 | catch (IdInUseException e) { 126 | String availableId = e.getMessage(); 127 | error("id " + id + " not available. Using: " + availableId); 128 | id = availableId; 129 | ruby.setId(id); 130 | } 131 | } // else we're still initializing and the initalizer should handle this 132 | } 133 | 134 | private String defaultId() { 135 | return Integer.toHexString(hashCode()); 136 | } 137 | 138 | public Atom[] getruby_version() { 139 | return rubyVersionValue; 140 | } 141 | 142 | // Using Atom[] is annoying but it avoids an annoying warning in the max menu "coerced float to String" when doing @ruby_version 1.9 143 | public void ruby_version(Atom[] rubyVersionValue) { 144 | if(initialized) { 145 | err("ruby_version cannot be changed. Use @ruby_version when creating the object."); 146 | return; 147 | } 148 | if(rubyVersionValue == null || rubyVersionValue.length < 1) return; 149 | 150 | String rubyVersionString = rubyVersionValue[0].toString(); 151 | while(rubyVersionString.endsWith("0")) { 152 | // @ruby_version 1.9 comes through as "1.900000" so we have to chop off the trailing zeros 153 | rubyVersionString = rubyVersionString.substring(0, rubyVersionString.length()-1); 154 | } 155 | 156 | this.rubyVersion = RubyProperties.getRubyVersion(rubyVersionString); 157 | if(this.rubyVersion == null) { 158 | err("Invalid ruby_version '" + rubyVersionString + "'. Only 1_8 and 1_9 supported. " + 159 | "Defaulting to version " + RubyProperties.DEFAULT_RUBY_VERSION_STRING + "."); 160 | this.rubyVersionValue = Atom.newAtom(new String[]{RubyProperties.DEFAULT_RUBY_VERSION_STRING}); 161 | rubyVersion = RubyProperties.DEFAULT_RUBY_VERSION; 162 | } else { 163 | this.rubyVersionValue = Atom.newAtom(new String[]{rubyVersionString}); 164 | } 165 | } 166 | 167 | @Override 168 | public void notifyDeleted() { 169 | if (ruby != null) { 170 | ruby.notifyDeleted(); 171 | } 172 | super.notifyDeleted(); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/ajm/maxsupport/Atomizer.java: -------------------------------------------------------------------------------- 1 | package ajm.maxsupport; 2 | 3 | import com.cycling74.max.Atom; 4 | 5 | /** 6 | * Interface for objects that can convert themselves to a Max Atom. 7 | * 8 | * @author Adam Murray (adam@compusition.com) 9 | */ 10 | public interface Atomizer { 11 | 12 | Atom toAtom(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/ajm/preemptrseq.java: -------------------------------------------------------------------------------- 1 | package ajm; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | import ajm.seqsupport.Item; 34 | import ajm.util.Utils; 35 | 36 | import com.cycling74.max.Atom; 37 | 38 | /** 39 | * Preemptive rseq, sends positive values out one bang early. 40 | * 41 | * @author Adam Murray (adam@compusition.com) 42 | */ 43 | public class preemptrseq extends rseq { 44 | 45 | List actualSeq = new ArrayList(); 46 | 47 | public preemptrseq(Atom[] args) { 48 | super(args); 49 | } 50 | 51 | @Override 52 | public void set(Atom[] list) { 53 | try { 54 | List newSeq = parser.parse(list); 55 | 56 | actualSeq.clear(); 57 | actualSeq.addAll(newSeq); 58 | setPreemptiveSeq(); 59 | 60 | onSeqChange(); 61 | } 62 | catch (IllegalStateException e) { 63 | err("Could not evaluate: " + Utils.detokenize(list) + "\n" + e.getMessage()); 64 | } 65 | } 66 | 67 | // This shouldn't be necessary 68 | @Override 69 | public void resetseq() { 70 | actualSeq.clear(); 71 | actualSeq.addAll(defaultSeq); 72 | setPreemptiveSeq(); 73 | onSeqChange(); 74 | outputseq(); 75 | } 76 | 77 | // The seq needs to look the same externally 78 | // but internally transform positive numbers n into -(n-1), -1 79 | // e.g. 16 4 3 -> -15 1 -3 1 -2 1 80 | private void setPreemptiveSeq() { 81 | seq.clear(); 82 | for (Item item : actualSeq) { 83 | Atom atom = item.getAtom(); 84 | if (Utils.isNumber(atom)) { 85 | int val = atom.toInt(); 86 | if (val > 1) { 87 | seq.add(new Item(-(val - 1))); 88 | seq.add(new Item(1)); 89 | } 90 | else { 91 | seq.add(item); 92 | } 93 | } 94 | else { 95 | seq.add(item); 96 | } 97 | } 98 | } 99 | 100 | @Override 101 | public Atom[] getseq() { 102 | Atom[] atoms = new Atom[actualSeq.size()]; 103 | for (int i = 0; i < actualSeq.size(); i++) { 104 | atoms[i] = actualSeq.get(i).getAtom(); 105 | } 106 | return atoms; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/ajm/print.java: -------------------------------------------------------------------------------- 1 | package ajm; 2 | 3 | import com.cycling74.max.Atom; 4 | 5 | import ajm.maxsupport.AbstractMaxObject; 6 | import ajm.util.Utils; 7 | 8 | public class print extends AbstractMaxObject { 9 | 10 | private boolean onlyanything = false; 11 | private boolean detokenize = false; 12 | 13 | public print() { 14 | declareAttribute("onlyanything"); 15 | declareAttribute("detokenize"); 16 | declareIO(1, 0); 17 | createInfoOutlet(false); 18 | } 19 | 20 | public void inlet(float value) { 21 | if (onlyanything) { 22 | super.inlet(value); 23 | } 24 | else { 25 | System.out.println("Recieved a float: " + value); 26 | } 27 | } 28 | 29 | public void inlet(int value) { 30 | if (onlyanything) { 31 | super.inlet(value); 32 | } 33 | else { 34 | System.out.println("Recieved an int: " + value); 35 | } 36 | } 37 | 38 | public void list(Atom[] list) { 39 | if (onlyanything) { 40 | super.list(list); 41 | } 42 | else { 43 | System.out.println("Received a list of length " + list.length + (list.length > 0 ? ":" : ".")); 44 | dumpList(list); 45 | } 46 | } 47 | 48 | public void anything(String msg, Atom[] args) { 49 | System.out.println("Received message '" + (detokenize ? Utils.detokenize(msg) : msg) + "' with " + args.length 50 | + " arguments" + (args.length > 0 ? ":" : ".")); 51 | dumpList(args); 52 | } 53 | 54 | private void dumpList(Atom[] list) { 55 | for (Atom atom : list) { 56 | if (atom == null) { 57 | System.out.println(" null (might have been a semicolon?)"); 58 | } 59 | else if (atom.isInt()) { 60 | System.out.println(" int: " + atom.getInt()); 61 | } 62 | else if (atom.isFloat()) { 63 | System.out.println(" float: " + atom.getFloat()); 64 | } 65 | else { 66 | System.out.println(" string: '" + (detokenize ? Utils.detokenize(atom.getString()) : atom.getString()) 67 | + "'"); 68 | } 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/ajm/rseq.java: -------------------------------------------------------------------------------- 1 | package ajm; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.Arrays; 31 | 32 | import ajm.seqsupport.Item; 33 | 34 | import com.cycling74.max.Atom; 35 | import com.cycling74.max.Executable; 36 | 37 | /** 38 | * The ajm.rseq MaxObject 39 | * 40 | * @author Adam Murray (adam@compusition.com) 41 | */ 42 | public class rseq extends seq { 43 | 44 | public rseq(Atom[] args) { 45 | super(args); // declares seq attributes 46 | parser.setEvalNotes(false); 47 | } 48 | 49 | @Override 50 | protected Executable getInitializer() { 51 | return new RseqInitializer(); 52 | } 53 | 54 | protected class RseqInitializer extends SeqInitializer { 55 | @Override 56 | public void execute() { 57 | super.execute(); 58 | } 59 | } 60 | 61 | protected static int INFO_OUTLET = 5; 62 | 63 | protected int count = 0; 64 | protected int duration = INDEX_SET_BEFORE_SEQ; 65 | protected static int INFINITY = -1; 66 | protected static int INDEX_SET_BEFORE_SEQ = -2; 67 | 68 | /* 69 | @Override 70 | protected void onSeqChange() { 71 | super.onSeqChange(); 72 | if (seq.size() > 0) { 73 | boolean allZeros = true; 74 | for (Item item : seq) { 75 | Object val = item.getValue(); 76 | if (val instanceof Atom) { 77 | Atom atom = (Atom) val; 78 | if (Utils.isNumber(atom) && atom.toInt() != 0) { 79 | allZeros = false; 80 | break; 81 | } 82 | } 83 | } 84 | if (allZeros) { 85 | seq.add(new Item(1)); // prevents infinite loops 86 | } 87 | } 88 | } 89 | */ 90 | 91 | @Override 92 | public void bang() { 93 | if (!seq.isEmpty()) { 94 | fixIndexBounds(); 95 | 96 | // debug("!! count=" + count + ", duration=" + duration); 97 | 98 | if (count >= duration) { 99 | if (duration == INDEX_SET_BEFORE_SEQ) { 100 | // handle case where @index is set before @seq 101 | // by reseting the index without advancing, to get the current value 102 | index(index); 103 | } 104 | else if (duration > 0) { 105 | if (!arpeggiating()) { 106 | index(index + step); 107 | } 108 | else { 109 | setDuration(); 110 | } 111 | } 112 | } 113 | 114 | // debug("count=" + count + ", duration=" + duration); 115 | 116 | if (count == 0) { 117 | output(); 118 | int startIndex = index; 119 | while (duration == 0 && duration != INFINITY) { 120 | if (!arpeggiating()) { 121 | index(index + step); 122 | if (index == startIndex) { 123 | // prevent infinite loop 124 | // we already advanced to the next value we want to output, 125 | // so prevent advancing on the next bang: 126 | duration = INDEX_SET_BEFORE_SEQ; 127 | break; 128 | } 129 | } 130 | else { 131 | setDuration(); 132 | } 133 | output(); 134 | } 135 | } 136 | } 137 | count++; 138 | } 139 | 140 | @Override 141 | public void index(int idx) { 142 | super.index(idx); 143 | setDuration(); 144 | } 145 | 146 | private void setDuration() { 147 | count = 0; 148 | if (seq.isEmpty()) { 149 | duration = INDEX_SET_BEFORE_SEQ; 150 | } 151 | else { 152 | fixIndexBounds(); 153 | Item item = seq.get(index); 154 | if (item.isInfinite()) { 155 | duration = INFINITY; 156 | return; 157 | } 158 | 159 | Object val = item.getValue(); 160 | if (val instanceof Atom) { 161 | Atom atom = (Atom) val; 162 | // Strings will be coerced to 0, which is exactly what we want (output immediately and advance) 163 | duration = Math.abs(atom.toInt()); 164 | } 165 | else if (chordmode == CHORDMODE.ARPEGGIATE) { 166 | duration = Math.abs(((Atom[]) val)[chordIndex].toInt()); 167 | } 168 | else { 169 | duration = 0; 170 | } 171 | } 172 | } 173 | 174 | @Override 175 | public void reset() { 176 | super.reset(); 177 | count = 0; 178 | duration = 0; 179 | } 180 | 181 | public boolean equals(Object obj) { 182 | if (obj instanceof rseq) { 183 | return Arrays.equals(getseq(), ((rseq) obj).getseq()); 184 | } 185 | else { 186 | return false; 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/ajm/rubysupport/AbstractScriptEvaluator.java: -------------------------------------------------------------------------------- 1 | package ajm.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | 33 | /** 34 | * Superclass for script evaluator implementations. 35 | * 36 | * @author Adam Murray (adam@compusition.com) 37 | */ 38 | public abstract class AbstractScriptEvaluator implements ScriptEvaluator { 39 | 40 | private boolean initialized = false; 41 | private Map persitentGlobals = new HashMap(); 42 | 43 | protected abstract void resetEngineContext(); 44 | 45 | protected abstract void declareGlobalInternal(String variableName, Object obj) throws Exception; 46 | 47 | protected abstract void undeclareGlobalInternal(String variableName) throws Exception; 48 | 49 | public void resetContext() { 50 | resetEngineContext(); 51 | try { 52 | for (Map.Entry global : persitentGlobals.entrySet()) { 53 | String name = global.getKey(); 54 | undeclareGlobalInternal(name); 55 | declareGlobalInternal(name, global.getValue()); 56 | } 57 | } 58 | catch (Exception e) { 59 | throw new RubyException(e); 60 | } 61 | } 62 | 63 | public boolean isInitialized() { 64 | return initialized; 65 | } 66 | 67 | public void setInitialized(boolean initialized) { 68 | this.initialized = initialized; 69 | } 70 | 71 | public void declareGlobal(String variableName, Object obj) { 72 | try { 73 | declareGlobalInternal(variableName, obj); 74 | } 75 | catch (Exception e) { 76 | throw new RubyException(e); 77 | } 78 | persitentGlobals.put(variableName, obj); 79 | } 80 | 81 | public void undeclareGlobal(String variableName) { 82 | try { 83 | undeclareGlobalInternal(variableName); 84 | } 85 | catch (Exception e) { 86 | throw new RubyException(e); 87 | } 88 | persitentGlobals.remove(variableName); 89 | } 90 | 91 | public void setScriptFilename(String scriptFilename) { 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/ajm/rubysupport/BSFRubyEvaluator.java: -------------------------------------------------------------------------------- 1 | package ajm.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import org.apache.bsf.BSFException; 31 | import org.apache.bsf.BSFManager; 32 | import org.jruby.CompatVersion; 33 | 34 | /** 35 | * Bridge to Apache BSF project's Ruby evaluator engine. 36 | * 37 | * @author Adam Murray (adam@compusition.com) 38 | */ 39 | public class BSFRubyEvaluator extends AbstractScriptEvaluator { 40 | 41 | private BSFManager manager; 42 | 43 | public BSFRubyEvaluator(CompatVersion version) { 44 | BSFManager.registerScriptingEngine("ruby", "org.jruby.javasupport.bsf.JRubyEngine", new String[] { "rb" }); 45 | resetEngineContext(); 46 | } 47 | 48 | protected void resetEngineContext() { 49 | manager = new BSFManager(); 50 | } 51 | 52 | protected void declareGlobalInternal(String variableName, Object obj) throws BSFException { 53 | manager.declareBean(variableName, obj, obj.getClass()); 54 | } 55 | 56 | protected void undeclareGlobalInternal(String variableName) throws BSFException { 57 | // BUG 58 | // It should be a best practice to undeclare any global varaibles before redeclaring them, 59 | // but when I try to do this, somehow $max_ruby_adapter and $global_variable_store can end up 60 | // null inside my scripts even though I can verify that it is being redeclared as a non-null value, 61 | // or not even being undeclared at all! 62 | 63 | // Commenting this out for now until I can figure out what's going on (probably a bug in JRuby?) 64 | // manager.undeclareBean(variableName); 65 | } 66 | 67 | public Object eval(CharSequence rubyCode) { 68 | try { 69 | return manager.eval("ruby", getClass().getName(), 1, 1, rubyCode); 70 | } 71 | catch (BSFException e) { 72 | throw new RubyException(e); 73 | } 74 | } 75 | 76 | public void exit() { 77 | manager.terminate(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ajm/rubysupport/IdInUseException.java: -------------------------------------------------------------------------------- 1 | package ajm.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | /** 31 | * A failure to assign an ID because it was already in use. 32 | * 33 | * The exception message will suggest an available id. 34 | * 35 | * @author Adam Murray (adam@compusition.com) 36 | */ 37 | @SuppressWarnings("serial") 38 | public class IdInUseException extends RuntimeException { 39 | 40 | public IdInUseException(String availableId) { 41 | super(availableId); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ajm/rubysupport/JRubyEmbedEvaluator.java: -------------------------------------------------------------------------------- 1 | package ajm.rubysupport; 2 | 3 | import org.jruby.CompatVersion; 4 | import org.jruby.embed.ScriptingContainer; 5 | import org.jruby.embed.LocalContextScope; 6 | import org.jruby.embed.LocalVariableBehavior; 7 | 8 | public class JRubyEmbedEvaluator extends AbstractScriptEvaluator { 9 | 10 | private ScriptingContainer container; 11 | 12 | private CompatVersion compatVersion; 13 | 14 | public JRubyEmbedEvaluator(CompatVersion rubyVersion) { 15 | // if compatVersion isn't set right away, it won't work (maybe because of the global var setup?) 16 | compatVersion = rubyVersion; 17 | resetEngineContext(); 18 | } 19 | 20 | protected void resetEngineContext() { 21 | container = new ScriptingContainer(LocalContextScope.SINGLETHREAD, LocalVariableBehavior.TRANSIENT); 22 | container.setCompatVersion(compatVersion); 23 | } 24 | 25 | protected void declareGlobalInternal(String variableName, Object obj) { 26 | container.put("$" + variableName, obj); 27 | } 28 | 29 | protected void undeclareGlobalInternal(String variableName) { 30 | container.removeAttribute("$" + variableName); 31 | } 32 | 33 | public void setScriptFilename(String scriptFilename) { 34 | if(scriptFilename == null) { 35 | scriptFilename = ""; 36 | } 37 | container.setScriptFilename(scriptFilename); 38 | } 39 | 40 | public Object eval(CharSequence rubyCode) { 41 | return container.runScriptlet(rubyCode.toString()); 42 | } 43 | 44 | public void exit() { 45 | container.terminate(); 46 | container = null; 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ajm/rubysupport/RubyException.java: -------------------------------------------------------------------------------- 1 | package ajm.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | /** 31 | * Wrapper for any Ruby exceptions. Helps encapsulate the underlying Ruby implementation. 32 | * 33 | * @author Adam Murray (adam@compusition.com) 34 | */ 35 | @SuppressWarnings("serial") 36 | public class RubyException extends RuntimeException { 37 | 38 | public RubyException() { 39 | super(); 40 | } 41 | 42 | public RubyException(String message, Throwable cause) { 43 | super(message, cause); 44 | } 45 | 46 | public RubyException(String message) { 47 | super(message); 48 | } 49 | 50 | public RubyException(Throwable cause) { 51 | super(cause); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ajm/rubysupport/RubyProperties.java: -------------------------------------------------------------------------------- 1 | package ajm.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.io.File; 31 | import java.io.FileInputStream; 32 | import java.io.IOException; 33 | import java.util.Properties; 34 | 35 | import org.jruby.CompatVersion; 36 | 37 | import com.cycling74.max.MaxSystem; 38 | 39 | /** 40 | * Manages all global settings for Ruby evaluation. 41 | * 42 | * @author Adam Murray (adam@compusition.com) 43 | */ 44 | public class RubyProperties { 45 | 46 | public static Properties properties; 47 | 48 | public static final String DEFAULT_RUBY_ENGINE = "ajm.rubysupport.JRubyEmbedEvaluator"; 49 | 50 | public static final String DEFAULT_INITIALIZER_FILES = "ajm_ruby_initialize.rb"; 51 | 52 | private static String rubyEngine; 53 | 54 | private static String[] initializers; 55 | 56 | private static String[] loadpaths; 57 | 58 | public static String getRubyEngine() { 59 | if (rubyEngine == null) { 60 | rubyEngine = properties.getProperty("ruby.engine", DEFAULT_RUBY_ENGINE).trim(); 61 | } 62 | return rubyEngine; 63 | } 64 | 65 | public static String[] getInitializerFiles() { 66 | if (initializers == null) { 67 | initializers = properties.getProperty("ruby.initializers", DEFAULT_INITIALIZER_FILES).split(";"); 68 | for (int i = 0; i < initializers.length; i++) { 69 | initializers[i] = initializers[i].trim(); 70 | } 71 | } 72 | return initializers; 73 | } 74 | 75 | public static String[] getLoadPaths() { 76 | if (loadpaths == null) { 77 | String loadpathsProp = properties.getProperty("ruby.loadpaths"); 78 | if (loadpathsProp == null || loadpathsProp.trim().equals("")) { 79 | loadpaths = new String[] {}; 80 | } 81 | else { 82 | loadpaths = loadpathsProp.split(";"); 83 | for (int i = 0; i < loadpaths.length; i++) { 84 | loadpaths[i] = loadpaths[i].trim(); 85 | } 86 | } 87 | } 88 | return loadpaths; 89 | } 90 | 91 | static { 92 | // Initialize JRuby system properties and load the ajm.ruby.properties file 93 | try { 94 | String propsPath = MaxSystem.locateFile("ajm.ruby.properties"); 95 | if (propsPath == null) { 96 | //MaxSystem.error("ajm.ruby.properties not found! Maybe ajm objects was not installed correctly?"); 97 | properties = new Properties(); 98 | } 99 | else { 100 | File propFile = new File(propsPath); 101 | properties = new Properties(); 102 | try { 103 | properties.load(new FileInputStream(propFile)); 104 | } 105 | catch (IOException e) { 106 | throw new RuntimeException(e); 107 | } 108 | 109 | String jrubyHome = properties.getProperty("jruby.home"); 110 | if(jrubyHome != null) { 111 | System.setProperty("jruby.home", jrubyHome); 112 | } 113 | } 114 | } 115 | catch (UnsatisfiedLinkError e) { 116 | // we're running outside of Max, probably for unit testing 117 | // can't call System.out here, Max stole it and it will just cause another UnsatisfiedLinkError 118 | // System.out.println("Using hard-coded defaults for RubyProperties."); 119 | properties = new Properties(); 120 | } 121 | } 122 | 123 | public static CompatVersion getRubyVersion(String version) { 124 | if("1.9".equals(version)) { 125 | return CompatVersion.RUBY1_9; 126 | } 127 | else if("1.8".equals(version)) { 128 | return CompatVersion.RUBY1_8; 129 | } 130 | else return null; 131 | } 132 | 133 | public static String DEFAULT_RUBY_VERSION_STRING = "1.8"; 134 | public static CompatVersion DEFAULT_RUBY_VERSION = CompatVersion.RUBY1_8; 135 | } 136 | -------------------------------------------------------------------------------- /src/ajm/rubysupport/ScriptEvaluator.java: -------------------------------------------------------------------------------- 1 | package ajm.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | /** 31 | * Interface for all script evaluator engines. 32 | * 33 | * @author Adam Murray (adam@compusition.com) 34 | */ 35 | public interface ScriptEvaluator { 36 | 37 | void resetContext(); 38 | 39 | boolean isInitialized(); 40 | 41 | void setInitialized(boolean initialized); 42 | 43 | void declareGlobal(String variableName, Object obj); 44 | 45 | void undeclareGlobal(String variableName); 46 | 47 | void setScriptFilename(String scriptFilename); 48 | 49 | Object eval(CharSequence rubyCode); 50 | 51 | void exit(); 52 | } 53 | -------------------------------------------------------------------------------- /src/ajm/rubysupport/SymbolConversionOption.java: -------------------------------------------------------------------------------- 1 | package ajm.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2010, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | public enum SymbolConversionOption { 31 | 32 | /** 33 | * Convert incoming Max symbols to Ruby strings 34 | */ 35 | STRING ("string"), 36 | 37 | /** 38 | * Convert incoming Max symbols to Ruby symbols 39 | */ 40 | SYMBOL ("symbol"), 41 | 42 | /** 43 | * Don't modify incoming Max symbols (so they can be interpreted as literal numbers, reference method names, etc) 44 | */ 45 | LITERAL ("literal"), 46 | 47 | /** 48 | * Apply the previous setting to the remaining inlets 49 | */ 50 | REMAINING_INLETS ("*"); 51 | 52 | public static SymbolConversionOption DEFAULT = LITERAL; 53 | 54 | SymbolConversionOption(String stringValue) { 55 | this.stringValue = stringValue; 56 | } 57 | 58 | /** 59 | * The string value of this enumerated value. 60 | */ 61 | public String toString() { 62 | return stringValue; 63 | } 64 | private final String stringValue; 65 | 66 | public static SymbolConversionOption fromString(String stringValue) { 67 | if(STRING.stringValue.equals(stringValue)) { 68 | return STRING; 69 | } 70 | else if(SYMBOL.stringValue.equals(stringValue)) { 71 | return SYMBOL; 72 | } 73 | else if(LITERAL.stringValue.equals(stringValue)) { 74 | return LITERAL; 75 | } 76 | else if(REMAINING_INLETS.stringValue.equals(stringValue)) { 77 | return REMAINING_INLETS; 78 | } 79 | else { 80 | return null; 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/ajm/seqsupport/Token.java: -------------------------------------------------------------------------------- 1 | package ajm.seqsupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import com.cycling74.max.Atom; 31 | 32 | /** 33 | * A token in the ajm objects sequencing syntax. 34 | * 35 | * @author Adam Murray (adam@compusition.com) 36 | */ 37 | public class Token { 38 | 39 | public enum TYPE { 40 | REPEAT_BEGIN, REPEAT_END, REPEAT_STAR, CHORD_BEGIN, CHORD_END, NEXT, PREV, RUBY_BEGIN, RUBY_END, TEXT; 41 | } 42 | 43 | private TYPE type; 44 | private String text; 45 | 46 | public Token(TYPE type) { 47 | this(type, null); 48 | } 49 | 50 | public Token(TYPE type, String text) { 51 | this.type = type; 52 | this.text = text; 53 | } 54 | 55 | public TYPE getType() { 56 | return type; 57 | } 58 | 59 | public int getInt() { 60 | return Integer.parseInt(text); 61 | } 62 | 63 | public String getText() { 64 | return text; 65 | } 66 | 67 | public Item getItem() { 68 | try { 69 | if (text.contains(".")) { 70 | return new Item(Float.parseFloat(text)); 71 | } 72 | else { 73 | return new Item(Integer.parseInt(text)); 74 | } 75 | } 76 | catch (NumberFormatException e) { 77 | return new Item(text); 78 | } 79 | } 80 | 81 | public Atom getAtom() { 82 | try { 83 | if (text.contains(".")) { 84 | return Atom.newAtom(Float.parseFloat(text)); 85 | } 86 | else { 87 | return Atom.newAtom(Integer.parseInt(text)); 88 | } 89 | } 90 | catch (NumberFormatException e) { 91 | return Atom.newAtom(text); 92 | } 93 | } 94 | 95 | public Atom getValue() { 96 | int val; 97 | switch (Character.toUpperCase(text.charAt(0))) { 98 | case 'C': 99 | val = 0; 100 | break; 101 | 102 | case 'D': 103 | val = 2; 104 | break; 105 | 106 | case 'E': 107 | val = 4; 108 | break; 109 | 110 | case 'F': 111 | val = 5; 112 | break; 113 | 114 | case 'G': 115 | val = 7; 116 | break; 117 | 118 | case 'A': 119 | val = 9; 120 | break; 121 | 122 | case 'B': 123 | val = 11; 124 | break; 125 | 126 | default: 127 | return getAtom(); 128 | 129 | } 130 | 131 | int i = 1; 132 | int quarterSteps = 0; 133 | loop: for (; i < text.length(); i++) { 134 | switch (text.charAt(i)) { 135 | case '#': 136 | val++; 137 | break; 138 | 139 | case 'b': 140 | val--; 141 | break; 142 | 143 | case '+': 144 | quarterSteps++; 145 | break; 146 | 147 | case '_': 148 | quarterSteps--; 149 | break; 150 | 151 | default: 152 | break loop; 153 | } 154 | } 155 | 156 | try { 157 | int octave = Integer.parseInt(text.substring(i)); 158 | val += (octave + 1) * 12; 159 | // only convert to float if absolutely necessary (floats introduce round off error) 160 | if (quarterSteps % 2 == 0) { 161 | return Atom.newAtom(val + quarterSteps / 2); 162 | } 163 | else { 164 | return Atom.newAtom(val + quarterSteps / 2.0f); 165 | } 166 | } 167 | catch (NumberFormatException e) { 168 | return Atom.newAtom(text); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/ajm/util/DummyMaxObject.java: -------------------------------------------------------------------------------- 1 | package ajm.util; 2 | 3 | import com.cycling74.max.MaxObject; 4 | 5 | /** 6 | * For Unit Testing 7 | */ 8 | public class DummyMaxObject extends MaxObject { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/ajm/util/FileWatcher.java: -------------------------------------------------------------------------------- 1 | package ajm.util; 2 | 3 | /* 4 | Copyright (c) 2008-2010, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.io.File; 31 | 32 | import com.cycling74.max.Executable; 33 | 34 | /** 35 | * File watch mechanism for Max objects. Executes a callback when the file modified date changes. 36 | * 37 | * @author Adam Murray (adam@compusition.com) 38 | */ 39 | 40 | public class FileWatcher extends Thread { 41 | 42 | public final static long DEFAULT_WATCH_PERIOD = 2500; // 2.5 seconds, for no particular reason 43 | 44 | private File file; 45 | 46 | private long prevLastModified; 47 | 48 | private long watchPeriod; 49 | 50 | private Executable callback; 51 | 52 | private volatile boolean threadPaused = false; 53 | 54 | private Thread thisThread; 55 | 56 | public FileWatcher(File file, Executable callback) { 57 | this(file, callback, DEFAULT_WATCH_PERIOD); 58 | } 59 | 60 | public FileWatcher(File file, Executable callback, long watchPeriod) { 61 | setFile(file); 62 | setCallback(callback); 63 | setWatchPeriod(watchPeriod); 64 | } 65 | 66 | public void setFile(File file) { 67 | this.file = file; 68 | if (file == null) { 69 | prevLastModified = -1; 70 | } 71 | else { 72 | prevLastModified = file.lastModified(); 73 | } 74 | } 75 | 76 | public File getFile() { 77 | return file; 78 | } 79 | 80 | public long getWatchPeriod() { 81 | return watchPeriod; 82 | } 83 | 84 | public void setWatchPeriod(long watchPeriod) { 85 | this.watchPeriod = watchPeriod; 86 | } 87 | 88 | public Executable getCallback() { 89 | return callback; 90 | } 91 | 92 | public void setCallback(Executable callback) { 93 | this.callback = callback; 94 | } 95 | 96 | public void run() { 97 | thisThread = Thread.currentThread(); 98 | while (true) { 99 | if (file != null) { 100 | long currLastMod = file.lastModified(); 101 | // System.out.println("Last modified = " + currLastMod); 102 | if (currLastMod > prevLastModified) { 103 | try { 104 | callback.execute(); 105 | } catch (Exception e) { 106 | if (e.getMessage() != null) { 107 | // not sure why the message would be null, but I've seen it happen occasionally 108 | System.err.println(e.getMessage()); 109 | } 110 | } 111 | prevLastModified = currLastMod; 112 | } 113 | try { 114 | Thread.sleep(watchPeriod); 115 | if (threadPaused) { 116 | synchronized (this) { 117 | while (threadPaused) 118 | wait(); // until notify(); 119 | } 120 | } 121 | } catch (InterruptedException e) { 122 | // System.err.println("FileWatcher interrupted (harmless, but probably shouldn't have happened)"); 123 | // e.printStackTrace(); 124 | } 125 | } 126 | } 127 | } 128 | 129 | public synchronized void stopWatching() { 130 | pauseWatching(); 131 | } 132 | 133 | public synchronized void pauseWatching() { 134 | threadPaused = true; 135 | } 136 | 137 | public synchronized void resumeWatching() { 138 | threadPaused = false; 139 | thisThread.notify(); // resume from wait(); 140 | } 141 | 142 | public synchronized boolean isPaused() { 143 | return threadPaused; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/ajm/util/GlobalVariableStore.java: -------------------------------------------------------------------------------- 1 | package ajm.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | import java.util.Set; 33 | 34 | /** 35 | * A singleton map for sharing global variables across any object in the JVM. 36 | * 37 | * @author Adam Murray (adam@compusition.com) 38 | */ 39 | public class GlobalVariableStore { 40 | 41 | private static GlobalVariableStore instance; 42 | 43 | private GlobalVariableStore() { 44 | } 45 | 46 | public synchronized static GlobalVariableStore getInstance() { 47 | if (instance == null) { 48 | instance = new GlobalVariableStore(); 49 | } 50 | return instance; 51 | } 52 | 53 | private Map variableStore = new HashMap(); 54 | 55 | public synchronized Object get(String name) { 56 | return variableStore.get(name); 57 | } 58 | 59 | public synchronized Object set(String name, Object value) { 60 | return variableStore.put(name, value); 61 | } 62 | 63 | public synchronized boolean delete(String name) { 64 | Object value = variableStore.remove(name); 65 | return value != null; 66 | } 67 | 68 | public synchronized boolean defined(String name) { 69 | return variableStore.containsKey(name); 70 | } 71 | 72 | public synchronized Set names() { 73 | return variableStore.keySet(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/ajm/util/LineBuilder.java: -------------------------------------------------------------------------------- 1 | package ajm.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | /** 31 | * Builds text one line at a time. 32 | * 33 | * @author Adam Murray (adam@compusition.com) 34 | */ 35 | public class LineBuilder implements CharSequence { 36 | 37 | private StringBuilder lines = new StringBuilder(); 38 | 39 | public void line(String s) { 40 | lines.append(s).append("\n"); 41 | } 42 | 43 | public void append(String s) { 44 | lines.append(s); 45 | } 46 | 47 | public String toString() { 48 | return lines.toString(); 49 | } 50 | 51 | public boolean isEmpty() { 52 | return lines.length() == 0; 53 | } 54 | 55 | public int length() { 56 | return lines.length(); 57 | } 58 | 59 | public void clear() { 60 | lines.setLength(0); 61 | } 62 | 63 | public char charAt(int index) { 64 | return lines.charAt(index); 65 | } 66 | 67 | public CharSequence subSequence(int start, int end) { 68 | return lines.subSequence(start, end); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/ajm/util/Logger.java: -------------------------------------------------------------------------------- 1 | package ajm.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | /** 31 | * Simple logging interface. 32 | * 33 | * @author Adam Murray (adam@compusition.com) 34 | */ 35 | public interface Logger { 36 | 37 | void debug(String message); 38 | 39 | void info(String message); 40 | 41 | void err(String message); 42 | } 43 | -------------------------------------------------------------------------------- /src/ajm/util/MappedSet.java: -------------------------------------------------------------------------------- 1 | package ajm.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.LinkedHashSet; 31 | import java.util.Set; 32 | import java.util.TreeMap; 33 | 34 | /** 35 | * A Map where all the values are a Set. The Set maintains insertion order of its entires. 36 | * 37 | * @author Adam Murray (adam@compusition.com) 38 | */ 39 | @SuppressWarnings("serial") 40 | public class MappedSet extends TreeMap> { 41 | 42 | public Set addValue(K key, T value) { 43 | Set values = get(key); 44 | if (values == null) { 45 | // LinkedHashSet maintains insertion order 46 | values = new LinkedHashSet(); 47 | put(key, values); 48 | } 49 | values.add(value); 50 | return values; 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | public MappedSet clone() { 55 | return (MappedSet) super.clone(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ajm/util/TextBlock.java: -------------------------------------------------------------------------------- 1 | package ajm.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | 33 | /** 34 | * A singleton Map of Strings. Used to indirectly pass long messages in Max. 35 | * 36 | * @author Adam Murray (adam@compusition.com) 37 | */ 38 | public class TextBlock { 39 | 40 | private static Map textMap = new HashMap(); 41 | 42 | private TextBlock() { 43 | } 44 | 45 | public synchronized static void set(String name, String text) { 46 | textMap.put(name, text); 47 | } 48 | 49 | public synchronized static String get(String name) { 50 | return textMap.get(name); 51 | } 52 | 53 | public synchronized static void remove(String name) { 54 | textMap.remove(name); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ajm/util/TextViewer.java: -------------------------------------------------------------------------------- 1 | package ajm.util; 2 | 3 | /* 4 | Copyright (c) 2008-2009, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.awt.BorderLayout; 31 | import java.awt.Dimension; 32 | import java.awt.Font; 33 | import java.awt.Toolkit; 34 | 35 | import javax.swing.JFrame; 36 | import javax.swing.JScrollPane; 37 | import javax.swing.JTextArea; 38 | import javax.swing.SwingUtilities; 39 | 40 | /** 41 | * A Swing-based popup window for viewing text. 42 | * 43 | * @author Adam Murray (adam@compusition.com) 44 | */ 45 | public class TextViewer { 46 | 47 | private JFrame frame; 48 | private JTextArea textArea; 49 | private boolean packed = false; 50 | private int width; 51 | private int height; 52 | Dimension dim; 53 | 54 | public TextViewer(String name) { 55 | frame = new JFrame(name); 56 | frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); 57 | width = 600; 58 | height = 450; 59 | 60 | textArea = new JTextArea(); 61 | textArea.setEditable(false); 62 | textArea.setFont(new Font("Monospaced", Font.PLAIN, 11)); 63 | 64 | JScrollPane scroller = new JScrollPane(textArea); 65 | scroller.setPreferredSize(new Dimension(width, height)); 66 | 67 | frame.add(scroller, BorderLayout.CENTER); 68 | } 69 | 70 | public void show() { 71 | SwingUtilities.invokeLater(new Runnable() { 72 | public void run() { 73 | if(!packed) { 74 | frame.pack(); 75 | packed = true; 76 | } 77 | frame.setVisible(true); 78 | } 79 | }); 80 | } 81 | 82 | public void hide() { 83 | frame.setVisible(false); 84 | } 85 | 86 | public void setText(String text) { 87 | textArea.setText(text); 88 | } 89 | 90 | private int WINDOW_PADDING = 10; 91 | public void setCenter(int x, int y) { 92 | x -= width/2; 93 | y -= height/2; 94 | 95 | if(x < WINDOW_PADDING) x = WINDOW_PADDING; 96 | if(y < WINDOW_PADDING) y = WINDOW_PADDING; 97 | 98 | Toolkit toolkit = Toolkit.getDefaultToolkit(); 99 | Dimension resolution = toolkit.getScreenSize(); 100 | int maxX = resolution.width - width - WINDOW_PADDING; 101 | int maxY = resolution.height - height - WINDOW_PADDING; 102 | if(x > maxX) x = maxX; 103 | if(y > maxY) y = maxY; 104 | 105 | frame.setLocation(x,y); 106 | } 107 | } 108 | --------------------------------------------------------------------------------