mtk 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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