mtk 0.0.2 → 0.0.3

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 (147) hide show
  1. data/.yardopts +3 -2
  2. data/DEVELOPMENT_NOTES.md +114 -0
  3. data/INTRO.md +64 -8
  4. data/LICENSE.txt +1 -1
  5. data/README.md +31 -102
  6. data/Rakefile +56 -18
  7. data/bin/mtk +215 -0
  8. data/examples/crescendo.rb +5 -5
  9. data/examples/drum_pattern1.rb +23 -0
  10. data/examples/dynamic_pattern.rb +8 -11
  11. data/examples/gets_and_play.rb +26 -0
  12. data/examples/notation.rb +22 -0
  13. data/examples/play_midi.rb +8 -10
  14. data/examples/random_tone_row.rb +2 -2
  15. data/examples/syntax_to_midi.rb +28 -0
  16. data/examples/test_output.rb +8 -0
  17. data/examples/tone_row_melody.rb +6 -6
  18. data/lib/mtk.rb +52 -40
  19. data/lib/mtk/chord.rb +55 -0
  20. data/lib/mtk/constants/durations.rb +57 -0
  21. data/lib/mtk/constants/intensities.rb +61 -0
  22. data/lib/mtk/constants/intervals.rb +73 -0
  23. data/lib/mtk/constants/pitch_classes.rb +29 -0
  24. data/lib/mtk/constants/pitches.rb +52 -0
  25. data/lib/mtk/duration.rb +211 -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/helpers/collection.rb +164 -0
  30. data/lib/mtk/helpers/convert.rb +36 -0
  31. data/lib/mtk/helpers/lilypond.rb +162 -0
  32. data/lib/mtk/helpers/output_selector.rb +67 -0
  33. data/lib/mtk/helpers/pitch_collection.rb +23 -0
  34. data/lib/mtk/helpers/pseudo_constants.rb +26 -0
  35. data/lib/mtk/intensity.rb +156 -0
  36. data/lib/mtk/interval.rb +155 -0
  37. data/lib/mtk/lang/mtk_grammar.citrus +190 -13
  38. data/lib/mtk/lang/parser.rb +29 -0
  39. data/lib/mtk/melody.rb +94 -0
  40. data/lib/mtk/midi/dls_synth_device.rb +144 -0
  41. data/lib/mtk/midi/dls_synth_output.rb +62 -0
  42. data/lib/mtk/midi/file.rb +67 -32
  43. data/lib/mtk/midi/input.rb +97 -0
  44. data/lib/mtk/midi/jsound_input.rb +36 -17
  45. data/lib/mtk/midi/jsound_output.rb +48 -46
  46. data/lib/mtk/midi/output.rb +195 -0
  47. data/lib/mtk/midi/unimidi_input.rb +117 -0
  48. data/lib/mtk/midi/unimidi_output.rb +121 -0
  49. data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
  50. data/lib/mtk/patterns/chain.rb +49 -0
  51. data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
  52. data/lib/mtk/patterns/cycle.rb +18 -0
  53. data/lib/mtk/patterns/for_each.rb +71 -0
  54. data/lib/mtk/patterns/function.rb +39 -0
  55. data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
  56. data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
  57. data/lib/mtk/patterns/pattern.rb +171 -0
  58. data/lib/mtk/patterns/sequence.rb +20 -0
  59. data/lib/mtk/pitch.rb +7 -6
  60. data/lib/mtk/pitch_class.rb +124 -46
  61. data/lib/mtk/pitch_class_set.rb +58 -35
  62. data/lib/mtk/sequencers/event_builder.rb +131 -0
  63. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  64. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  65. data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
  66. data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
  67. data/lib/mtk/timeline.rb +39 -22
  68. data/lib/mtk/variable.rb +32 -0
  69. data/spec/mtk/chord_spec.rb +83 -0
  70. data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
  71. data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
  72. data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
  73. data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
  74. data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
  75. data/spec/mtk/duration_spec.rb +372 -0
  76. data/spec/mtk/events/event_spec.rb +234 -0
  77. data/spec/mtk/events/note_spec.rb +174 -0
  78. data/spec/mtk/events/parameter_spec.rb +220 -0
  79. data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
  80. data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
  81. data/spec/mtk/intensity_spec.rb +289 -0
  82. data/spec/mtk/interval_spec.rb +265 -0
  83. data/spec/mtk/lang/parser_spec.rb +597 -0
  84. data/spec/mtk/melody_spec.rb +223 -0
  85. data/spec/mtk/midi/file_spec.rb +16 -16
  86. data/spec/mtk/midi/jsound_input_spec.rb +11 -0
  87. data/spec/mtk/midi/jsound_output_spec.rb +11 -0
  88. data/spec/mtk/midi/output_spec.rb +102 -0
  89. data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
  90. data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
  91. data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
  92. data/spec/mtk/patterns/chain_spec.rb +110 -0
  93. data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
  94. data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
  95. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  96. data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
  97. data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
  98. data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
  99. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  100. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  101. data/spec/mtk/pitch_class_set_spec.rb +23 -21
  102. data/spec/mtk/pitch_class_spec.rb +151 -39
  103. data/spec/mtk/pitch_spec.rb +22 -1
  104. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  105. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  106. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  107. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  108. data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
  109. data/spec/mtk/timeline_spec.rb +109 -16
  110. data/spec/mtk/variable_spec.rb +52 -0
  111. data/spec/spec_coverage.rb +2 -0
  112. data/spec/spec_helper.rb +3 -0
  113. metadata +188 -91
  114. data/lib/mtk/_constants/durations.rb +0 -80
  115. data/lib/mtk/_constants/intensities.rb +0 -81
  116. data/lib/mtk/_constants/intervals.rb +0 -85
  117. data/lib/mtk/_constants/pitch_classes.rb +0 -35
  118. data/lib/mtk/_constants/pitches.rb +0 -49
  119. data/lib/mtk/event.rb +0 -70
  120. data/lib/mtk/helper/collection.rb +0 -114
  121. data/lib/mtk/helper/event_builder.rb +0 -85
  122. data/lib/mtk/helper/pseudo_constants.rb +0 -26
  123. data/lib/mtk/lang/grammar.rb +0 -17
  124. data/lib/mtk/note.rb +0 -63
  125. data/lib/mtk/pattern/abstract_pattern.rb +0 -132
  126. data/lib/mtk/pattern/cycle.rb +0 -51
  127. data/lib/mtk/pattern/enumerator.rb +0 -26
  128. data/lib/mtk/pattern/function.rb +0 -46
  129. data/lib/mtk/pattern/sequence.rb +0 -30
  130. data/lib/mtk/pitch_set.rb +0 -84
  131. data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
  132. data/lib/mtk/transform/invertible.rb +0 -15
  133. data/lib/mtk/transform/mappable.rb +0 -18
  134. data/lib/mtk/transform/set_theory_operations.rb +0 -34
  135. data/lib/mtk/transform/transposable.rb +0 -14
  136. data/spec/mtk/event_spec.rb +0 -139
  137. data/spec/mtk/helper/event_builder_spec.rb +0 -92
  138. data/spec/mtk/lang/grammar_spec.rb +0 -100
  139. data/spec/mtk/note_spec.rb +0 -115
  140. data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
  141. data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
  142. data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
  143. data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
  144. data/spec/mtk/pattern/sequence_spec.rb +0 -151
  145. data/spec/mtk/pitch_set_spec.rb +0 -198
  146. data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
  147. data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +0 -49
@@ -1,80 +0,0 @@
1
- require 'rational'
2
-
3
- module MTK
4
-
5
- # Defines duration constants using abbreviations for standard rhythm values ('w' for whole note, 'h' for half note, etc).
6
- #
7
- # These can be thought of like constants, but in order to distinguish 'e' (eighth note) from the {PitchClass} 'E'
8
- # it was necessary to use lower-case names and therefore define them as "pseudo constant" methods.
9
- # The methods are available either through the module (MTK::Durations::e) or via mixin (include MTK::Durations; e)
10
- #
11
- # These values assume the quarter note is one beat (1.0), so they work best with 4/4 and other */4 time signatures.
12
- #
13
- # @note Including this module defines a bunch of single-character variables, which may shadow existing variable names.
14
- # Just be mindful of what is defined in this module when including it.
15
- #
16
- # @see Note
17
- module Durations
18
- extend Helper::PseudoConstants
19
-
20
- # NOTE: the yard doc macros here only fill in [$2] with the actual value when generating docs under Ruby 1.9+
21
-
22
- # whole note
23
- # @macro [attach] durations.define_constant
24
- # @attribute [r]
25
- # @return [$2] number of beats for $1
26
- define_constant 'w', 4
27
-
28
- # half note
29
- define_constant 'h', 2
30
-
31
- # quarter note
32
- define_constant 'q', 1
33
-
34
- # eight note
35
- define_constant 'e', Rational(1,2)
36
-
37
- # sixteenth note
38
- define_constant 's', Rational(1,4)
39
-
40
- # thirty-second note
41
- define_constant 'r', Rational(1,8)
42
-
43
- # sixty-fourth note
44
- define_constant 'x', Rational(1,16)
45
-
46
- # The values of all "psuedo constants" defined in this module
47
- DURATIONS = [w, h, q, e, s, r, x].freeze
48
-
49
- # The names of all "psuedo constants" defined in this module
50
- DURATION_NAMES = %w[w h q e s r x].freeze
51
-
52
- # Lookup the value of an duration constant by name.
53
- # This method supports appending any combination of '.' and 't' for more fine-grained values.
54
- # each '.' multiplies by 3/2, and each 't' multiplies by 2/3.
55
- # @example lookup value of 'e.' (eight note), which is 0.75 (0.5 * 1.5)
56
- # MTK::Durations['e.']
57
- def self.[](name)
58
- begin
59
- modifier = nil
60
- if name =~ /(\w)((.|t)*)/
61
- name = $1
62
- modifier = $2
63
- end
64
-
65
- value = send name
66
- modifier.each_char do |mod|
67
- case mod
68
- when '.' then value *= Rational(3,2)
69
- when 't' then value *= Rational(2,3)
70
- end
71
- end
72
- value
73
-
74
- rescue
75
- nil
76
- end
77
- end
78
-
79
- end
80
- end
@@ -1,81 +0,0 @@
1
- module MTK
2
-
3
- # Defines intensity constants using standard dynamic symbols.
4
- #
5
- # These can be thought of like constants, but in order to distinguish 'f' (forte) from the {PitchClass} 'F'
6
- # it was necessary to use lower-case names and therefore define them as "pseudo constant" methods.
7
- # The methods are available either through the module (MTK::Intensities::f) or via mixin (include MTK::Intensities; f)
8
- #
9
- # These values are intensities in the range 0.125 - 1.0 (in increments of 1/8), so they can be easily scaled (unlike MIDI velocities).
10
- #
11
- # It is also possible to retrieve values in increments of 1/24 by using the '+' and '-' suffix when looking
12
- # up values via the {.[]} method.
13
- #
14
- # @note Including this module shadows Ruby's built-in p() method.
15
- # If you include this module, you can access the built-in p() method via Kernel.p()
16
- #
17
- # @see Note
18
- module Intensities
19
- extend Helper::PseudoConstants
20
-
21
- # NOTE: the yard doc macros here only fill in [$2] with the actual value when generating docs under Ruby 1.9+
22
-
23
- # pianississimo
24
- # @macro [attach] intensities.define_constant
25
- # @attribute [r]
26
- # @return [$2] intensity value for $1
27
- define_constant 'ppp', 0.125
28
-
29
- # pianissimo
30
- define_constant 'pp', 0.25
31
-
32
- # piano
33
- # @note Including this module shadows Ruby's built-in p() method.
34
- # If you include this module, you can access the built-in p() method via Kernel.p()
35
- define_constant 'p', 0.375
36
-
37
- # mezzo-piano
38
- define_constant 'mp', 0.5
39
-
40
- # mezzo-forte
41
- define_constant 'mf', 0.625
42
-
43
- # forte
44
- define_constant 'f', 0.75
45
-
46
- # fortissimo
47
- define_constant 'ff', 0.875
48
-
49
- # fortississimo
50
- define_constant 'fff', 1.0
51
-
52
- # The values of all "psuedo constants" defined in this module
53
- INTENSITIES = [ppp, pp, p, mp, mf, f, ff, fff].freeze
54
-
55
- # The names of all "psuedo constants" defined in this module
56
- INTENSITY_NAMES = %w[ppp pp p mp mf f ff fff].freeze
57
-
58
- # Lookup the value of an intensity constant by name.
59
- # This method supports appending '+' or '-' for more fine-grained values.
60
- # '+' and '-' add and subtract 1/24, respectively (enforcing the upper bound of 1.0 for 'fff+').
61
- # @example lookup value of 'mp+', which is 0.5416666666666666 (0.5 + 1/24.0)
62
- # MTK::Intensities['mp+']
63
- def self.[](name)
64
- return 1.0 if name == "fff+" # special case because "fff" is already the maximum
65
-
66
- modifier = nil
67
- if name =~ /(\w+)([+-])/
68
- name = $1
69
- modifier = $2
70
- end
71
-
72
- value = send name
73
- value += 1.0/24 if modifier == '+'
74
- value -= 1.0/24 if modifier == '-'
75
- value
76
- rescue
77
- nil
78
- end
79
-
80
- end
81
- end
@@ -1,85 +0,0 @@
1
- module MTK
2
-
3
- # Defines a constant for intervals up to an octave using diatonic naming conventions (see http://en.wikipedia.org/wiki/Interval_(music)#Main_intervals)
4
- #
5
- # Naming conventions
6
- # P#: perfect interval
7
- # M#: major interval
8
- # m#: minor interval
9
- # TT: tritone (AKA augmented 4th or diminshed 5th)
10
- #
11
- # These can be thought of like constants, but in order to succintly distinguish 'm2' (minor) from 'M2' (major),
12
- # it was necessary to use lower-case names for some of the values and therefore define them as "pseudo constant" methods.
13
- # The methods are available either through the module (MTK::Intervals::m2) or via mixin (include MTK::Intervals; m2)
14
- module Intervals
15
- extend Helper::PseudoConstants
16
-
17
- # NOTE: the yard doc macros here only fill in [$2] with the actual value when generating docs under Ruby 1.9+
18
-
19
- # perfect unison
20
- # @macro [attach] interval.define_constant
21
- # @attribute [r]
22
- # @return [$2] number of semitones in the interval $1
23
- define_constant 'P1', 0
24
-
25
- # minor second
26
- # @macro [attach] interval.define_constant
27
- # @attribute [r]
28
- # @return [$2] number of semitones in the interval $1
29
- define_constant 'm2', 1
30
-
31
- # major second
32
- define_constant 'M2', 2
33
-
34
- # minor third
35
- define_constant 'm3', 3
36
-
37
- # major third
38
- define_constant 'M3', 4
39
-
40
- # pefect fourth
41
- define_constant 'P4', 5
42
-
43
- # tritone (AKA augmented fourth or diminished fifth)
44
- define_constant 'TT', 6
45
-
46
- # perfect fifth
47
- define_constant 'P5', 7
48
-
49
- # minor sixth
50
- define_constant 'm6', 8
51
-
52
- # major sixth
53
- define_constant 'M6', 9
54
-
55
- # minor seventh
56
- define_constant 'm7', 10
57
-
58
- # major seventh
59
- define_constant 'M7', 11
60
-
61
- # pefect octave
62
- define_constant 'P8', 12
63
-
64
- # The values of all "psuedo constants" defined in this module
65
- INTERVALS = [P1, m2, M2, m3, M3, P4, TT, P5, m6, M6, m7, M7, P8].freeze
66
-
67
- # The names of all "psuedo constants" defined in this module
68
- INTERVAL_NAMES = %w[P1 m2 M2 m3 M3 P4 TT P5 m6 M6 m7 M7 P8].freeze
69
-
70
- # Lookup the value of an interval constant by name.
71
- # @example lookup value of 'M3', which is 4
72
- # MTK::Intervals['M3']
73
- def self.[](name)
74
- send name
75
- rescue
76
- begin
77
- const_get name
78
- rescue
79
- nil
80
- end
81
- end
82
-
83
- end
84
-
85
- end
@@ -1,35 +0,0 @@
1
- module MTK
2
-
3
- # Defines a constant for each {PitchClass} in the Western chromatic scale.
4
-
5
- module PitchClasses
6
-
7
- # The values of all "psuedo constants" defined in this module
8
- PITCH_CLASSES = []
9
-
10
- # The names of all "psuedo constants" defined in this module
11
- PITCH_CLASS_NAMES = PitchClass::NAMES
12
-
13
- for name in PITCH_CLASS_NAMES
14
- pc = PitchClass[name]
15
- PITCH_CLASSES << pc
16
- const_set name, pc
17
- end
18
-
19
- PITCH_CLASSES.freeze
20
-
21
- # Lookup the value of an pitch class constant by name.
22
- # @example lookup value of 'C'
23
- # MTK::PitchClasses['C']
24
- # @see PitchClass.[]
25
- def self.[](name)
26
- begin
27
- const_get name
28
- rescue
29
- nil
30
- end
31
- end
32
-
33
- end
34
-
35
- end
@@ -1,49 +0,0 @@
1
- module MTK
2
-
3
- # Defines a constants for each {Pitch} in the standard MIDI range using scientific pitch notation.
4
- #
5
- # See http://en.wikipedia.org/wiki/Scientific_pitch_notation
6
- #
7
- # @note Because the character '#' cannot be used in the name of a constant,
8
- # the "black key" pitches are all named as flats with 'b' (for example, Gb3 or Cb4)
9
- # @note Because the character '-' (minus) cannot be used in the name of a constant,
10
- # the low pitches use '_' (underscore) in place of '-' (minus) (for example C_1).
11
- module Pitches
12
-
13
- # The values of all "psuedo constants" defined in this module
14
- PITCHES = []
15
-
16
- # The names of all "psuedo constants" defined in this module
17
- PITCH_NAMES = []
18
-
19
- 128.times do |note_number|
20
- pitch = Pitch.from_i( note_number )
21
- PITCHES << pitch
22
-
23
- octave_str = pitch.octave.to_s.sub(/-/,'_') # '_1' for -1
24
- name = "#{pitch.pitch_class}#{octave_str}"
25
- PITCH_NAMES << name
26
-
27
- const_set name, pitch
28
- end
29
-
30
- PITCHES.freeze
31
- PITCH_NAMES.freeze
32
-
33
- # Lookup the value of an pitch constant by name.
34
- # @example lookup value of 'C3'
35
- # MTK::Pitches['C3']
36
- # @see Pitch.from_s
37
- # @note Unlike {Pitch.from_s} this method will accept either '_' (underscore) or '-' (minus) and treat it like '-' (minus)
38
- # @note Unlike {Pitch.from_s} this method only accepts the accidental 'b'
39
- def self.[](name)
40
- begin
41
- const_get name.sub('-','_')
42
- rescue
43
- nil
44
- end
45
- end
46
-
47
- end
48
-
49
- end
data/lib/mtk/event.rb DELETED
@@ -1,70 +0,0 @@
1
- module MTK
2
-
3
- # An abstract musical event that has an intensity and a duration
4
- # @abstract
5
- class Event
6
-
7
- # intensity of the note as a value in the range 0.0 - 1.0
8
- attr_reader :intensity
9
-
10
- def initialize(intensity, duration)
11
- @intensity, @duration = intensity, duration
12
- end
13
-
14
- def self.from_hash(hash)
15
- new hash[:intensity], hash[:duration]
16
- end
17
-
18
- def to_hash
19
- { :intensity => @intensity, :duration => @duration }
20
- end
21
-
22
- def clone_with(hash)
23
- self.class.from_hash(to_hash.merge hash)
24
- end
25
-
26
- def scale_intensity(scaling_factor)
27
- clone_with :intensity => @intensity * scaling_factor.to_f
28
- end
29
-
30
- def scale_duration(scaling_factor)
31
- clone_with :duration => @duration * scaling_factor.to_f
32
- end
33
-
34
- # intensity scaled to the MIDI range 0-127
35
- def velocity
36
- @velocity ||= (127 * @intensity).round
37
- end
38
-
39
- # Duration of the Event in beats (e.g. 1.0 is a quarter note in 4/4 time signatures)
40
- # This is the absolute value of the duration attribute used to construct the object.
41
- # @see rest?
42
- def duration
43
- @abs_duration ||= @duration.abs
44
- end
45
-
46
- # By convention, any events with negative durations are considered a rest
47
- def rest?
48
- @duration < 0
49
- end
50
-
51
- def duration_in_pulses(pulses_per_beat)
52
- @duration_in_pulses ||= (duration * pulses_per_beat).round
53
- end
54
-
55
- def == other
56
- other.respond_to? :intensity and @intensity == other.intensity and
57
- other.respond_to? :duration and @duration == other.duration
58
- end
59
-
60
- def to_s
61
- "#{sprintf '%.2f',@intensity}, #{sprintf '%.2f',@duration}"
62
- end
63
-
64
- def inspect
65
- "#@intensity, #@duration"
66
- end
67
-
68
- end
69
-
70
- end
@@ -1,114 +0,0 @@
1
- module MTK::Helper
2
-
3
- # Given a method #elements, which returns an Array of elements in the collection,
4
- # including this module will make the class Enumerable and provide various methods you'd expect from an Array.
5
- module Collection
6
- include Enumerable
7
-
8
- # A mutable array of elements in this collection
9
- def to_a
10
- Array.new(elements) # we construct a new array since some including classes make elements be immutable
11
- end
12
-
13
- # The number of elements in this collection
14
- def size
15
- elements.size
16
- end
17
- alias length size
18
-
19
- def empty?
20
- elements.nil? or elements.size == 0
21
- end
22
-
23
- # The each iterator for providing Enumerable functionality
24
- def each &block
25
- elements.each &block
26
- end
27
-
28
- # The first element
29
- def first(n=nil)
30
- n ? elements.first(n) : elements.first
31
- end
32
-
33
- # The last element
34
- def last(n=nil)
35
- n ? elements.last(n) : elements.last
36
- end
37
-
38
- # The element with the given index
39
- def [](index)
40
- elements[index]
41
- end
42
-
43
- def repeat(times=2)
44
- full_repetitions, fractional_repetitions = times.floor, times%1 # split into int and fractional part
45
- repeated = elements * full_repetitions
46
- repeated += elements[0...elements.size*fractional_repetitions]
47
- clone_with repeated
48
- end
49
-
50
- def permute
51
- clone_with elements.shuffle
52
- end
53
- alias shuffle permute
54
-
55
- def rotate(offset=1)
56
- clone_with elements.rotate(offset)
57
- end
58
-
59
- def concat(other)
60
- other_elements = (other.respond_to? :elements) ? other.elements : other
61
- clone_with(elements + other_elements)
62
- end
63
-
64
- def reverse
65
- clone_with elements.reverse
66
- end
67
- alias retrograde reverse
68
-
69
- def ==(other)
70
- if other.respond_to? :elements
71
- if other.respond_to? :options
72
- elements == other.elements and @options == other.options
73
- else
74
- elements == other.elements
75
- end
76
- else
77
- elements == other
78
- end
79
- end
80
-
81
- # Create a copy of the collection.
82
- # In order to use this method, the including class must implement .from_a()
83
- def clone
84
- clone_with to_a
85
- end
86
-
87
- #################################
88
- private
89
-
90
- # "clones" the object with the given elements, attempting to maintain any @options
91
- # This is designed to work with 2 argument constructors: def initialize(elements, options={})
92
- def clone_with elements
93
- from_a = self.class.method(:from_a)
94
- if from_a.arity == -2
95
- from_a[elements, (@options || {})]
96
- else
97
- from_a[elements]
98
- end
99
- end
100
-
101
- end
102
-
103
- end
104
-
105
- if not Array.instance_methods.include? :rotate
106
- # Array#rotate is only available in Ruby 1.9, so we may have to implement it
107
- class Array
108
- def rotate(offset=1)
109
- return self if empty?
110
- offset %= length
111
- self[offset..-1]+self[0...offset]
112
- end
113
- end
114
- end