jmtk 0.0.3.3-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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
|
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
|