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.
- data/.yardopts +10 -0
- data/DEVELOPMENT_NOTES.md +115 -0
- data/INTRO.md +129 -0
- data/LICENSE.txt +27 -0
- data/README.md +50 -0
- data/Rakefile +102 -0
- data/bin/jmtk +250 -0
- data/bin/mtk +250 -0
- data/examples/crescendo.rb +20 -0
- data/examples/drum_pattern.rb +23 -0
- data/examples/dynamic_pattern.rb +36 -0
- data/examples/gets_and_play.rb +27 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +17 -0
- data/examples/print_midi.rb +13 -0
- data/examples/random_tone_row.rb +18 -0
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +7 -0
- data/examples/tone_row_melody.rb +23 -0
- data/lib/mtk.rb +76 -0
- data/lib/mtk/core/duration.rb +213 -0
- data/lib/mtk/core/intensity.rb +158 -0
- data/lib/mtk/core/interval.rb +157 -0
- data/lib/mtk/core/pitch.rb +154 -0
- data/lib/mtk/core/pitch_class.rb +194 -0
- data/lib/mtk/events/event.rb +119 -0
- data/lib/mtk/events/note.rb +112 -0
- data/lib/mtk/events/parameter.rb +54 -0
- data/lib/mtk/events/timeline.rb +232 -0
- data/lib/mtk/groups/chord.rb +56 -0
- data/lib/mtk/groups/collection.rb +196 -0
- data/lib/mtk/groups/melody.rb +96 -0
- data/lib/mtk/groups/pitch_class_set.rb +163 -0
- data/lib/mtk/groups/pitch_collection.rb +23 -0
- data/lib/mtk/io/dls_synth_device.rb +146 -0
- data/lib/mtk/io/dls_synth_output.rb +62 -0
- data/lib/mtk/io/jsound_input.rb +87 -0
- data/lib/mtk/io/jsound_output.rb +82 -0
- data/lib/mtk/io/midi_file.rb +209 -0
- data/lib/mtk/io/midi_input.rb +97 -0
- data/lib/mtk/io/midi_output.rb +195 -0
- data/lib/mtk/io/notation.rb +162 -0
- data/lib/mtk/io/unimidi_input.rb +117 -0
- data/lib/mtk/io/unimidi_output.rb +140 -0
- data/lib/mtk/lang/durations.rb +57 -0
- data/lib/mtk/lang/intensities.rb +61 -0
- data/lib/mtk/lang/intervals.rb +73 -0
- data/lib/mtk/lang/mtk_grammar.citrus +237 -0
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/lang/pitch_classes.rb +29 -0
- data/lib/mtk/lang/pitches.rb +52 -0
- data/lib/mtk/lang/pseudo_constants.rb +26 -0
- data/lib/mtk/lang/variable.rb +32 -0
- data/lib/mtk/numeric_extensions.rb +66 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/patterns/choice.rb +43 -0
- data/lib/mtk/patterns/cycle.rb +18 -0
- data/lib/mtk/patterns/for_each.rb +71 -0
- data/lib/mtk/patterns/function.rb +39 -0
- data/lib/mtk/patterns/lines.rb +54 -0
- data/lib/mtk/patterns/palindrome.rb +45 -0
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/sequencers/event_builder.rb +132 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/sequencers/sequencer.rb +111 -0
- data/lib/mtk/sequencers/step_sequencer.rb +26 -0
- data/spec/mtk/core/duration_spec.rb +372 -0
- data/spec/mtk/core/intensity_spec.rb +289 -0
- data/spec/mtk/core/interval_spec.rb +265 -0
- data/spec/mtk/core/pitch_class_spec.rb +343 -0
- data/spec/mtk/core/pitch_spec.rb +297 -0
- data/spec/mtk/events/event_spec.rb +234 -0
- data/spec/mtk/events/note_spec.rb +174 -0
- data/spec/mtk/events/parameter_spec.rb +220 -0
- data/spec/mtk/events/timeline_spec.rb +430 -0
- data/spec/mtk/groups/chord_spec.rb +85 -0
- data/spec/mtk/groups/collection_spec.rb +374 -0
- data/spec/mtk/groups/melody_spec.rb +225 -0
- data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
- data/spec/mtk/io/midi_file_spec.rb +243 -0
- data/spec/mtk/io/midi_output_spec.rb +102 -0
- data/spec/mtk/lang/durations_spec.rb +89 -0
- data/spec/mtk/lang/intensities_spec.rb +101 -0
- data/spec/mtk/lang/intervals_spec.rb +143 -0
- data/spec/mtk/lang/parser_spec.rb +603 -0
- data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
- data/spec/mtk/lang/pitches_spec.rb +56 -0
- data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
- data/spec/mtk/lang/variable_spec.rb +52 -0
- data/spec/mtk/numeric_extensions_spec.rb +83 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/patterns/choice_spec.rb +97 -0
- data/spec/mtk/patterns/cycle_spec.rb +123 -0
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/patterns/function_spec.rb +120 -0
- data/spec/mtk/patterns/lines_spec.rb +77 -0
- data/spec/mtk/patterns/palindrome_spec.rb +108 -0
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
- data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
- data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
- data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
- data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/test.mid +0 -0
- metadata +226 -0
data/.yardopts
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
MTK
|
2
|
+
===
|
3
|
+
|
4
|
+
[](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
|
data/INTRO.md
ADDED
@@ -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
|
+
```
|
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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).
|
data/Rakefile
ADDED
@@ -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
|
data/bin/jmtk
ADDED
@@ -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
|