jmtk 0.0.3.3-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. data/.yardopts +10 -0
  2. data/DEVELOPMENT_NOTES.md +115 -0
  3. data/INTRO.md +129 -0
  4. data/LICENSE.txt +27 -0
  5. data/README.md +50 -0
  6. data/Rakefile +102 -0
  7. data/bin/jmtk +250 -0
  8. data/bin/mtk +250 -0
  9. data/examples/crescendo.rb +20 -0
  10. data/examples/drum_pattern.rb +23 -0
  11. data/examples/dynamic_pattern.rb +36 -0
  12. data/examples/gets_and_play.rb +27 -0
  13. data/examples/notation.rb +22 -0
  14. data/examples/play_midi.rb +17 -0
  15. data/examples/print_midi.rb +13 -0
  16. data/examples/random_tone_row.rb +18 -0
  17. data/examples/syntax_to_midi.rb +28 -0
  18. data/examples/test_output.rb +7 -0
  19. data/examples/tone_row_melody.rb +23 -0
  20. data/lib/mtk.rb +76 -0
  21. data/lib/mtk/core/duration.rb +213 -0
  22. data/lib/mtk/core/intensity.rb +158 -0
  23. data/lib/mtk/core/interval.rb +157 -0
  24. data/lib/mtk/core/pitch.rb +154 -0
  25. data/lib/mtk/core/pitch_class.rb +194 -0
  26. data/lib/mtk/events/event.rb +119 -0
  27. data/lib/mtk/events/note.rb +112 -0
  28. data/lib/mtk/events/parameter.rb +54 -0
  29. data/lib/mtk/events/timeline.rb +232 -0
  30. data/lib/mtk/groups/chord.rb +56 -0
  31. data/lib/mtk/groups/collection.rb +196 -0
  32. data/lib/mtk/groups/melody.rb +96 -0
  33. data/lib/mtk/groups/pitch_class_set.rb +163 -0
  34. data/lib/mtk/groups/pitch_collection.rb +23 -0
  35. data/lib/mtk/io/dls_synth_device.rb +146 -0
  36. data/lib/mtk/io/dls_synth_output.rb +62 -0
  37. data/lib/mtk/io/jsound_input.rb +87 -0
  38. data/lib/mtk/io/jsound_output.rb +82 -0
  39. data/lib/mtk/io/midi_file.rb +209 -0
  40. data/lib/mtk/io/midi_input.rb +97 -0
  41. data/lib/mtk/io/midi_output.rb +195 -0
  42. data/lib/mtk/io/notation.rb +162 -0
  43. data/lib/mtk/io/unimidi_input.rb +117 -0
  44. data/lib/mtk/io/unimidi_output.rb +140 -0
  45. data/lib/mtk/lang/durations.rb +57 -0
  46. data/lib/mtk/lang/intensities.rb +61 -0
  47. data/lib/mtk/lang/intervals.rb +73 -0
  48. data/lib/mtk/lang/mtk_grammar.citrus +237 -0
  49. data/lib/mtk/lang/parser.rb +29 -0
  50. data/lib/mtk/lang/pitch_classes.rb +29 -0
  51. data/lib/mtk/lang/pitches.rb +52 -0
  52. data/lib/mtk/lang/pseudo_constants.rb +26 -0
  53. data/lib/mtk/lang/variable.rb +32 -0
  54. data/lib/mtk/numeric_extensions.rb +66 -0
  55. data/lib/mtk/patterns/chain.rb +49 -0
  56. data/lib/mtk/patterns/choice.rb +43 -0
  57. data/lib/mtk/patterns/cycle.rb +18 -0
  58. data/lib/mtk/patterns/for_each.rb +71 -0
  59. data/lib/mtk/patterns/function.rb +39 -0
  60. data/lib/mtk/patterns/lines.rb +54 -0
  61. data/lib/mtk/patterns/palindrome.rb +45 -0
  62. data/lib/mtk/patterns/pattern.rb +171 -0
  63. data/lib/mtk/patterns/sequence.rb +20 -0
  64. data/lib/mtk/sequencers/event_builder.rb +132 -0
  65. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  66. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  67. data/lib/mtk/sequencers/sequencer.rb +111 -0
  68. data/lib/mtk/sequencers/step_sequencer.rb +26 -0
  69. data/spec/mtk/core/duration_spec.rb +372 -0
  70. data/spec/mtk/core/intensity_spec.rb +289 -0
  71. data/spec/mtk/core/interval_spec.rb +265 -0
  72. data/spec/mtk/core/pitch_class_spec.rb +343 -0
  73. data/spec/mtk/core/pitch_spec.rb +297 -0
  74. data/spec/mtk/events/event_spec.rb +234 -0
  75. data/spec/mtk/events/note_spec.rb +174 -0
  76. data/spec/mtk/events/parameter_spec.rb +220 -0
  77. data/spec/mtk/events/timeline_spec.rb +430 -0
  78. data/spec/mtk/groups/chord_spec.rb +85 -0
  79. data/spec/mtk/groups/collection_spec.rb +374 -0
  80. data/spec/mtk/groups/melody_spec.rb +225 -0
  81. data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
  82. data/spec/mtk/io/midi_file_spec.rb +243 -0
  83. data/spec/mtk/io/midi_output_spec.rb +102 -0
  84. data/spec/mtk/lang/durations_spec.rb +89 -0
  85. data/spec/mtk/lang/intensities_spec.rb +101 -0
  86. data/spec/mtk/lang/intervals_spec.rb +143 -0
  87. data/spec/mtk/lang/parser_spec.rb +603 -0
  88. data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
  89. data/spec/mtk/lang/pitches_spec.rb +56 -0
  90. data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
  91. data/spec/mtk/lang/variable_spec.rb +52 -0
  92. data/spec/mtk/numeric_extensions_spec.rb +83 -0
  93. data/spec/mtk/patterns/chain_spec.rb +110 -0
  94. data/spec/mtk/patterns/choice_spec.rb +97 -0
  95. data/spec/mtk/patterns/cycle_spec.rb +123 -0
  96. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  97. data/spec/mtk/patterns/function_spec.rb +120 -0
  98. data/spec/mtk/patterns/lines_spec.rb +77 -0
  99. data/spec/mtk/patterns/palindrome_spec.rb +108 -0
  100. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  101. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  102. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  103. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  104. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  105. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  106. data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
  107. data/spec/spec_coverage.rb +2 -0
  108. data/spec/spec_helper.rb +12 -0
  109. data/spec/test.mid +0 -0
  110. metadata +226 -0
@@ -0,0 +1,10 @@
1
+ --readme README.md
2
+ --title 'MTK (Music Tool Kit for Ruby)'
3
+ --charset utf-8
4
+ --protected
5
+ --no-private
6
+ lib/**/*.rb
7
+ - INTRO.md
8
+ - lib/**/*.citrus
9
+ - LICENSE.txt
10
+ - examples/**/*.rb
@@ -0,0 +1,115 @@
1
+ MTK
2
+ ===
3
+
4
+ [![Build Status](https://secure.travis-ci.org/adamjmurray/mtk.png)](http://travis-ci.org/adamjmurray/mtk)
5
+
6
+ Music Tool Kit for Ruby
7
+ -----------------------
8
+
9
+ Classes for modeling music with a focus on simplicity. Support for reading/writing MIDI files and realtime MIDI.
10
+
11
+
12
+
13
+ Getting Started
14
+ ---------------
15
+
16
+ gem install mtk
17
+
18
+ or download the source from here and add mtk/lib to your $LOAD_PATH. Then...
19
+
20
+ require 'mtk'
21
+
22
+ Some examples are available in the examples folder (more coming soon).
23
+ The specs provide a lot of details of usage...
24
+
25
+
26
+
27
+ Goals
28
+ -----
29
+
30
+ * Build musical generators to assist with composing music
31
+ * Re-implement Cosy (http://compusition.com/web/software/cosy) using these models as the "backend"
32
+
33
+
34
+
35
+ Status
36
+ ------
37
+
38
+ Alpha phase, API subject to change. Feedback welcome!
39
+
40
+
41
+
42
+ Requirements
43
+ ------------
44
+
45
+ ### Ruby Version
46
+
47
+ Ruby 1.9+ or JRuby 1.6+
48
+
49
+
50
+ ### Dependencies
51
+
52
+ MTK's core features should not depend on anything outside of the Ruby standard library.
53
+
54
+
55
+ MTK's optional features typically require gems. Currently the following gems are required:
56
+
57
+ * MIDI file I/O requires the __midilib__ gem
58
+
59
+ * realtime MIDI I/O with (MRI/YARV) Ruby requires the __unimidi__ and __gamelan__ gems
60
+
61
+ * realtime MIDI I/O with JRuby require the __jsound__ and __gamelan__ gems
62
+
63
+ * The custom MTK syntax (work in progress) requires the __citrus__ gem
64
+
65
+
66
+ Development requires the gems for optional features, plus the following:
67
+
68
+ * rake
69
+ * rspec (tests)
70
+ * yard (docs)
71
+
72
+ You shouldn't need to worry about the dependencies too much. A Gemfile is provided to sort this out for you:
73
+
74
+ gem install bundler
75
+ bundle install
76
+
77
+ [rvm](https://rvm.beginrescueend.com/) is required for cross version testing (see Development Notes below)
78
+
79
+
80
+
81
+ Documentation
82
+ -------------
83
+
84
+ Generate with:
85
+
86
+ bundle exec rake doc
87
+
88
+ Or view online @ http://rubydoc.info/github/adamjmurray/mtk/master/frames
89
+
90
+
91
+ Development Notes
92
+ -----------------
93
+
94
+ ### Run Tests ###
95
+
96
+ Test with current version of Ruby:
97
+
98
+ bundle exec rake test
99
+
100
+ Test with all supported versions of Ruby (requires [rvm](https://rvm.beginrescueend.com/), YARV 1.9.3, and JRuby 1.6.7):
101
+
102
+ bundle exec rake test:all
103
+
104
+ The test:all test must pass for a pull request to be accepted or for a release of the mtk gem.
105
+
106
+
107
+ ### Generate Docs ###
108
+
109
+ bundle exec rake doc
110
+ open doc/frames.html
111
+
112
+ or, to automatically refresh the documentation as you work:
113
+
114
+ bundle exec yard server -r
115
+ open http://localhost:8808
@@ -0,0 +1,129 @@
1
+ (Work in progress)
2
+
3
+ Core Concepts
4
+ -------------
5
+
6
+ ### Core data types
7
+
8
+ These model the basic properties of musical events:
9
+ * PitchClass
10
+ * Pitch
11
+ * Duration
12
+ * Intensity
13
+
14
+ These types are all immutable. This helps avoid confusing bugs.
15
+ Mostly you don't need to worry about this. Just remember when you call methods that change the value, like #invert,
16
+ it does not change the value in-place. For example:
17
+
18
+ p = PitchClass[G]
19
+ p = p.invert(E) # because p.invert(E) does not change p
20
+
21
+ Intensity values are intended to range from 0.0 (minimum intensity) to 1.0 (maximum intensity).
22
+
23
+ A Duration of 1 is one beat (usually a quarter note, depending on the meter). By convention, negative durations
24
+ indicate a rest that lasts for the absolute value duration.
25
+
26
+
27
+ <br/>
28
+ ### Events
29
+
30
+ Events group together the core data types together to represent a musical event, like a single Note.
31
+ Events can be converted to and from MIDI.
32
+
33
+ * event
34
+ * note
35
+
36
+
37
+ <br/>
38
+ ### Core collections
39
+
40
+ A collection of timed events, such as a melody, a riff, or one track of an entire song.
41
+
42
+ * timeline
43
+
44
+
45
+ <br/>
46
+ ### Patterns
47
+
48
+ Structured collections of core data types and other patterns. Used to generate musical material.
49
+
50
+ * cycle
51
+ * palindrome
52
+ * choice (randomly select from weghted distribution)
53
+ * line (linear interpolation between 2 points)
54
+ * function (dynamically generate elements with a lambda)
55
+
56
+ Future?
57
+ * curve (exponential/curved interpolation between points)
58
+ * permutation (cycle that permutes itself each cycle period)
59
+ * markov chain
60
+ * graph
61
+ * petri net (special type of graph??)
62
+ * custom (just implement enumerator pattern?)
63
+
64
+
65
+ <br/>
66
+ ### Sequencers
67
+
68
+ Convert patterns into timelines
69
+
70
+
71
+ <br/>
72
+ ### Syntax
73
+
74
+ Basic Tenants
75
+
76
+ * Minimal syntax: less typing means more music!
77
+ * Be case-insensitive to avoid typos caused by lower vs upper case (there's one notable exception for intervals: m2 vs M2)
78
+ * pitch and duration are the most important properties
79
+ * pitch is more important than rhythm
80
+
81
+ Because pitch class and duration are such important properties, and we want to minimize typing, we represent these with 1 letter.
82
+
83
+ **Diatonic Pitch Class: c d e f g a b**
84
+
85
+ **Chromatic Pitch Class (Sharp/Flat): c c# db d d# eb e e# fb f f# gb g g# ab a a# bb b b#**
86
+
87
+ Double-sharps (c##) and double-flats (dbb) are also allowed. You may prefer Bb to bb, etc
88
+
89
+ **Pitch (Pitch Class + Octave Number): c4 db5 c-1 g9**
90
+
91
+ c-1 (that's octave number: negative 1) is the lowest note. g9 is the highest.
92
+
93
+ **Duration: w h q i s r x (that's: whole note, half, quarter, eighth, sixteenth, thirty-second, sixty-fourth)**
94
+
95
+ Why "i", "r", and "x"? We're trying to keep these one letter. 'e', 'r', and 's' were already used,
96
+ so we just move along the letters of the word until we find an unused letter.
97
+
98
+ For intensity, we'll try to use standard music notation, but 'f' conflicts, so we handle this the same way we did with durations:
99
+
100
+ **Intensity: ppp pp p mp mf o ff fff**
101
+
102
+ (You may find it helpful to think "loud" for "o")
103
+
104
+
105
+ TODO: keep documenting this...
106
+
107
+
108
+ Summary of single-letter assignments:
109
+ ```
110
+ a -> pitch class a
111
+ b -> pitch class b, flat modifier on pitch class
112
+ c -> pitch class c
113
+ d -> pitch class d
114
+ e -> pitch class e
115
+ f -> pitch class f
116
+
117
+ h -> half note duration
118
+ i -> eighth note duration
119
+
120
+ o -> forte intensity
121
+ p -> piano intensity
122
+ q -> quarter note duration
123
+ r -> thirty-second note duration
124
+ s -> sixteenth note duration
125
+ t -> triplet modifier on durations
126
+
127
+ w -> whole note duration
128
+ x -> sixty-fourth note duration
129
+ ```
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2011-2013, Adam Murray (adam@compusition.com).
2
+
3
+ All rights reserved.
4
+
5
+ Redistribution and use of the "MTK" Ruby library, in source and binary forms,
6
+ with or without modification, are permitted provided that the following
7
+ conditions 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.
@@ -0,0 +1,50 @@
1
+ MTK: a Music Tool Kit for Ruby
2
+ ===
3
+
4
+ MTK is a Ruby library and custom [domain-specific language](https://en.wikipedia.org/wiki/Domain-specific_language) (DSL) for generating musical material.
5
+
6
+ MTK is flexible: You may use the custom music-generating language without writing any Ruby code, or you may avoid the custom language
7
+ and only program in Ruby, or anywhere in between.
8
+
9
+
10
+ Features
11
+ --------
12
+ * A minimalist syntax (DSL) for generating musical patterns
13
+ * Read and write MIDI files
14
+ * Record and output realtime MIDI signals
15
+ * Sequence MIDI-compatible event streams
16
+ * Manipulate musical objects like pitch, duration, and intensity
17
+ * Generate deterministic and non-deterministic music via flexible patterns such as looping sequences and random choices
18
+
19
+ Getting Started
20
+ ---------------
21
+
22
+ MTK works with Ruby 1.9, Ruby 2.0, and JRuby
23
+
24
+ 0. Install
25
+
26
+ gem install mtk
27
+
28
+ or if using JRuby:
29
+
30
+ jgem install mtk
31
+
32
+ 0. Learn the command-line interface:
33
+
34
+ mtk --help
35
+
36
+ 0. Learn the MTK syntax: TODO... documentation forthcoming. In the meantime, see the unit tests @ https://github.com/adamjmurray/mtk/blob/master/spec/mtk/lang/parser_spec.rb
37
+
38
+ 0. Check out examples: https://github.com/adamjmurray/mtk/tree/master/examples
39
+
40
+ 0. Read the [MTK Ruby library documentation](http://rubydoc.info/github/adamjmurray/mtk/master/frames)
41
+
42
+
43
+ About this project
44
+ ------------------
45
+ This project is developed by [Adam Murray (github.com/adamjmurray)](http://github.com/adamjmurray).
46
+
47
+ It is a free and open source project licensed under [a permissive BSD-style license](https://raw.github.com/adamjmurray/mtk/master/LICENSE.txt).
48
+ I simply ask for credit by including my copyright in derivative works.
49
+
50
+ You can learn more about the development of this project at the [development notes page](https://github.com/adamjmurray/mtk/blob/master/DEVELOPMENT_NOTES.md).
@@ -0,0 +1,102 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'rake/clean'
3
+
4
+ GEM_VERSION = '0.0.3.3'
5
+
6
+ SUPPORTED_RUBIES = %w[ 1.9.3 2.0 jruby-1.7.4 ]
7
+ ENV['JRUBY_OPTS'] = '--1.9'
8
+
9
+ task :default => :test
10
+
11
+ CLEAN.include('html','doc','coverage.data','coverage', '*.gem') # clean and clobber do the same thing for now
12
+
13
+ desc "Run RSpec tests with full output"
14
+ RSpec::Core::RakeTask.new('test') do |spec|
15
+ spec.rspec_opts = ["--color", "--format", "nested"]
16
+ if ARGV[1]
17
+ # only run specs with filenames starting with the command line argument
18
+ spec.pattern = "spec/**/#{ARGV[1]}*"
19
+ end
20
+ end
21
+
22
+ task :spec => :test
23
+
24
+
25
+ namespace :gem do
26
+ desc "Install gems for supported versions of Ruby: #{SUPPORTED_RUBIES.join ', '}"
27
+ task :install_dependencies do
28
+ fail unless system("rvm #{SUPPORTED_RUBIES.join ','} do bundle install")
29
+ end
30
+
31
+ desc "Build the CRuby and JRuby gems for distribution"
32
+ task :build do
33
+ gem_version = GEM_VERSION
34
+
35
+ gem_name = 'mtk'
36
+ platform_specific_depedencies = {unimidi:'~> 0.3'}
37
+ additional_gem_specifications = {}
38
+ generate_gemspec(binding)
39
+
40
+ gem_name = 'jmtk'
41
+ platform_specific_depedencies = {jsound:'~> 0.1'}
42
+ additional_gem_specifications = {platform:'java'}
43
+ generate_gemspec(binding)
44
+ end
45
+
46
+ def generate_gemspec(erb_bindings)
47
+ gem_name = erb_bindings.eval('gem_name')
48
+
49
+ erb = ERB.new(IO.read 'mtk.gemspec.erb')
50
+ gemspec = erb.result(erb_bindings)
51
+
52
+ gemspec_filename = "#{gem_name}.gemspec"
53
+ puts "Generating #{gemspec_filename}"
54
+ IO.write(gemspec_filename, gemspec)
55
+
56
+ if gem_name == 'jmtk'
57
+ `cp bin/mtk bin/jmtk` # jmtk gem uses this as the binary
58
+ end
59
+ puts "Building gem"
60
+ puts `gem build #{gemspec_filename}`
61
+ ensure
62
+ `rm bin/jmtk`
63
+ end
64
+ end
65
+
66
+
67
+ namespace :test do
68
+ desc "Run RSpec tests with summary output and fast failure"
69
+ RSpec::Core::RakeTask.new(:fast) do |spec|
70
+ spec.rspec_opts = ["--color", "--fail-fast"]
71
+ end
72
+
73
+ desc "Run RSpec tests and generate a coverage report"
74
+ if RUBY_PLATFORM == "java"
75
+ task :cov do |t|
76
+ fail "#{t} task is not compatible with JRuby. Use Ruby 1.9 instead."
77
+ end
78
+ else
79
+ RSpec::Core::RakeTask.new(:cov) do |spec|
80
+ spec.rspec_opts = ["--color", "-r", "#{File.dirname __FILE__}/spec/spec_coverage.rb"]
81
+ end
82
+ end
83
+
84
+ desc "Profile RSpec tests and report 10 slowest"
85
+ RSpec::Core::RakeTask.new(:prof) do |spec|
86
+ spec.rspec_opts = ["--color", "-p"]
87
+ end
88
+
89
+ desc "Run RSpec tests on all supported versions of Ruby: #{SUPPORTED_RUBIES.join ', '}"
90
+ task :all do
91
+ fail unless system("rvm #{SUPPORTED_RUBIES.join ','} do bundle exec rake -f #{__FILE__} test:fast")
92
+ end
93
+ end
94
+
95
+
96
+ begin
97
+ require 'yard'
98
+ YARD::Rake::YardocTask.new(:doc) do |yard|
99
+ yard.files = ['lib/**/*.rb']
100
+ end
101
+ rescue Exception # yard is optional, so don't cause rake to fail if it's missing
102
+ end
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mtk'
4
+ require 'optparse'
5
+
6
+ options = {}
7
+
8
+
9
+ #######################################################################
10
+
11
+ option_parser = OptionParser.new do |opts|
12
+
13
+ opts.banner = "Usage: #{$0} [options]"
14
+ opts.separator ''
15
+ opts.separator 'Options:'
16
+
17
+ opts.on('-c FILE', '--convert FILE', 'Convert a file containing MTK syntax to MIDI',
18
+ 'if a --file is given, write the MIDI to a file',
19
+ 'if an --output is given, play the MIDI',
20
+ 'otherwise print the MIDI') {|file| options[:convert] = file }
21
+
22
+ opts.separator ''
23
+
24
+ opts.on('-e [syntax]', '--eval [syntax]', 'Convert the given MTK syntax String to MIDI',
25
+ 'or start an interactive interpreter when [syntax] is omitted',
26
+ 'Behaves like --convert when a --file or --output is given') {|syntax| options[:eval] = syntax }
27
+
28
+ opts.separator ''
29
+
30
+ opts.on('-f FILE', '--file FILE', 'Write the output of --convert, --eval, --input, or --watch to a file'
31
+ ) {|file| options[:file] = file }
32
+
33
+ opts.separator ''
34
+
35
+ opts.on('-h', '--help', 'Show this message') { puts opts; exit }
36
+
37
+ opts.separator ''
38
+
39
+ opts.on('-i INPUT', '--input INPUT', 'Set MIDI input for recording',
40
+ 'if no --file is specified, prints the recorded MIDI') {|input| options[:input] = input }
41
+
42
+ opts.separator ''
43
+
44
+ opts.on('-l', '--list', 'List available MIDI devices for --input and --output') { options[:list] = true }
45
+
46
+ opts.separator ''
47
+
48
+ opts.on('-m', '--monitor', 'Monitor MIDI input while recording') { options[:monitor] = true }
49
+
50
+ opts.separator ''
51
+
52
+ opts.on('-o OUTPUT', '--output OUTPUT', 'Set MIDI output for playing') {|output| options[:output] = output }
53
+
54
+ opts.separator ''
55
+
56
+ opts.on('-p FILE', '--play FILE', 'Play or print the contents of a MIDI file',
57
+ 'if no --output is specified, print the MIDI file') {|file| options[:play] = file }
58
+
59
+ opts.separator ''
60
+
61
+ #opts.on('-t', '--tutorial', 'Start an interactive tutorial for the MTK syntax') { options[:tutorial] = true }
62
+ #
63
+ #opts.separator ''
64
+
65
+ opts.on('-w FILE', '--watch FILE', 'Watch an MTK syntax file for changes and automatically convert to MIDI',
66
+ 'Behaves like --convert when a --file or --output is given') {|file| options[:watch] = file }
67
+
68
+ end
69
+
70
+
71
+ #######################################################################
72
+
73
+ puts option_parser and exit if ARGV.length == 0
74
+ #p ARGV
75
+ #p options
76
+
77
+ ERROR_INVALID_COMMAND = 1
78
+ ERROR_FILE_NOT_FOUND = 2
79
+ ERROR_OUTPUT_NOT_FOUND = 3
80
+ ERROR_INPUT_NOT_FOUND = 4
81
+
82
+ # Immediately trying to play output while Ruby is still "warming up" can cause timing issues with
83
+ # the first couple notes. So we play this "empty" Timeline containing a rest to address that issue.
84
+ WARMUP = MTK::Events::Timeline.from_h( {0 => MTK.Note(60,-1)} )
85
+
86
+ #######################################################################
87
+
88
+ begin
89
+ option_parser.parse!
90
+ rescue OptionParser::MissingArgument, OptionParser::InvalidOption
91
+ puts "Invalid command, #{$!}"
92
+ puts "For command line help: #{$0} --help"
93
+ puts "For command line help: #{$0} --help"
94
+ exit ERROR_INVALID_COMMAND
95
+ end
96
+
97
+
98
+ def setup_io
99
+ require 'mtk/io/midi_input'
100
+ require 'mtk/io/midi_output'
101
+ end
102
+
103
+
104
+ def convert(mtk_syntax)
105
+ sequencer = MTK::Lang::Parser.parse(mtk_syntax)
106
+ if sequencer
107
+ timeline = sequencer.to_timeline
108
+ output(timeline)
109
+ end
110
+ rescue Citrus::ParseError
111
+ STDERR.puts $!
112
+ end
113
+
114
+
115
+ def output(timelines, print_header='Timeline')
116
+ timelines = [timelines] unless timelines.is_a? Array
117
+ if @output
118
+ @output.play WARMUP
119
+ @output.play timelines.first # TODO: support multiple timelines
120
+ elsif @file
121
+ require 'mtk/io/midi_file'
122
+ MTK.MIDIFile(@file).write timelines
123
+ else
124
+ puts print_header, timelines
125
+ puts
126
+ end
127
+ end
128
+
129
+
130
+ def record
131
+ if @input
132
+ print "Press Enter to begin recording MIDI input..."
133
+ gets
134
+ puts "Recording input. Press control-C to stop."
135
+ @input.record monitor:@monitor
136
+ Signal.trap("INT") do # SIGINT = control-C
137
+ @input.stop
138
+ output @input.to_timeline, "\nRecorded MIDI data"
139
+ exit
140
+ end
141
+ loop{ sleep 0.01 }
142
+ end
143
+ end
144
+
145
+
146
+ def watch_file_updated?
147
+ mtime = File.stat(@watch_file).mtime
148
+ updated = @watch_file_mtime.nil? || @watch_file_mtime < mtime
149
+ @watch_file_mtime = mtime
150
+ updated
151
+ end
152
+
153
+
154
+ #######################################################################
155
+
156
+ if options[:list]
157
+ setup_io
158
+ input_names = MTK::IO::MIDIInput.devices_by_name.keys
159
+ output_names = MTK::IO::MIDIOutput.devices_by_name.keys
160
+ puts
161
+ puts (['INPUTS:'] + input_names).join("\n * ")
162
+ puts
163
+ puts (['OUTPUTS:']+output_names).join("\n * ")
164
+ puts
165
+ puts 'When specifying --input INPUT or --output OUTPUT, the first substring match will be used.'
166
+ puts "For example: --output IAC will use 'Apple Inc. IAC Driver' if it's the first OUTPUT containing 'IAC'"
167
+ puts
168
+ exit
169
+ end
170
+
171
+ @monitor = true if options[:monitor]
172
+
173
+ if options[:input]
174
+ setup_io
175
+ input_name = options[:input]
176
+ @input = MTK::IO::MIDIInput.find_by_name /#{input_name}/
177
+ if @input
178
+ puts "Using input '#{@input.name}'"
179
+ else
180
+ STDERR.puts "Input '#{input_name}' not found."
181
+ exit ERROR_INPUT_NOT_FOUND
182
+ end
183
+ end
184
+
185
+ if options[:output]
186
+ setup_io
187
+ output_name = options[:output]
188
+ @output = MTK::IO::MIDIOutput.find_by_name /#{output_name}/
189
+ if @output
190
+ puts "Using output '#{@output.name}'"
191
+ else
192
+ STDERR.puts "Output '#{output_name}' not found."
193
+ exit ERROR_OUTPUT_NOT_FOUND
194
+ end
195
+ end
196
+
197
+ file = options[:file]
198
+ @file = file if file
199
+
200
+ if options[:play]
201
+ filename = options[:play]
202
+ require 'mtk/io/midi_file'
203
+ timelines = MTK.MIDIFile(filename).to_timelines
204
+ output(timelines, "Timeline for #{filename}")
205
+ end
206
+
207
+ if options.has_key? :eval
208
+ mtk_syntax = options[:eval]
209
+ if mtk_syntax.nil?
210
+ puts "Starting the interactive interpreter."
211
+ begin
212
+ loop do
213
+ puts "Enter MTK syntax. Press Ctrl+C to exit."
214
+ convert(gets)
215
+ end
216
+ rescue SystemExit,Interrupt
217
+ Kernel.exit
218
+ end
219
+ else
220
+ convert(mtk_syntax)
221
+ end
222
+ end
223
+
224
+ if options[:convert]
225
+ mtk_syntax_file = options[:convert]
226
+ mtk_syntax = IO.read(mtk_syntax_file)
227
+ convert(mtk_syntax)
228
+ end
229
+
230
+ if options[:watch]
231
+ @watch_file = options[:watch]
232
+ puts "Watching #{@watch_file}. Press Ctrl+C to exit."
233
+ watch_file_updated? # prime the watcher
234
+ begin
235
+ loop do
236
+ mtk_syntax = IO.read(@watch_file)
237
+ convert(mtk_syntax)
238
+ Kernel.sleep(0.5) until watch_file_updated?
239
+ puts "#{Time.new}: #{@watch_file} updated"
240
+ end
241
+ rescue SystemExit,Interrupt
242
+ Kernel.exit
243
+ end
244
+ end
245
+
246
+ #if options.has_key? :tutorial
247
+ # puts "TODO: tutorial"
248
+ #end
249
+
250
+ record if @input