Class: MIDI::Track

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/midilib/track.rb

Overview

A Track is a list of events.

When you modify the events array, make sure to call recalc_times so each Event gets its time_from_start recalculated.

A Track also holds a bitmask that specifies the channels used by the track. This bitmask is set when the track is read from the MIDI file by an IO::SeqReader but is not kept up to date by any other methods.

Constant Summary collapse

UNNAMED =
'Unnamed'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sequence) ⇒ Track

Returns a new instance of Track.



22
23
24
25
26
27
28
29
30
# File 'lib/midilib/track.rb', line 22

def initialize(sequence)
  @sequence = sequence
  @events = []

  # Bitmask of all channels used. Set when track is read in from
  # a MIDI file.
  @channels_used = 0
  @instrument = nil
end

Instance Attribute Details

#channels_usedObject

Returns the value of attribute channels_used.



19
20
21
# File 'lib/midilib/track.rb', line 19

def channels_used
  @channels_used
end

#eventsObject

Returns the value of attribute events.



19
20
21
# File 'lib/midilib/track.rb', line 19

def events
  @events
end

#sequenceObject (readonly)

Returns the value of attribute sequence.



20
21
22
# File 'lib/midilib/track.rb', line 20

def sequence
  @sequence
end

Instance Method Details

#delete_event(event, call_recalc_times = true) ⇒ Object

If ‘event` exists in @events, deletes it, updates the delta time of the event after it, and calls `recalc_times` by default.



69
70
71
72
73
74
75
76
# File 'lib/midilib/track.rb', line 69

def delete_event(event, call_recalc_times = true)
  i = @events.index(event)
  return unless i

  @events[i + 1].delta_time += @events[i].delta_time if i != (@events.length - 1)
  @events.delete_at(i)
  recalc_times if call_recalc_times
end

#each(&block) ⇒ Object

Iterates over events, yielding each one.



63
64
65
# File 'lib/midilib/track.rb', line 63

def each(&block) # :yields: event
  @events.each(&block)
end

#ensure_track_end_meta_eventObject

Makes sure that we have one and only one end track meta event at the end of this track.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/midilib/track.rb', line 80

def ensure_track_end_meta_event
  track_ends = @events.select { |e| e.is_a?(MetaEvent) && e.meta_type == META_TRACK_END }
  has_end = !@events.empty? && track_ends[-1] == @events.last

  # If we only have one end event and it's the last one, there's nothing
  # to do.
  return if track_ends.length == 1 && has_end

  # If we have an end of track event already, leave it alone.
  track_ends.pop if has_end
  track_ends.each { |track_end| delete_event(track_end, false) }
  return if has_end

  mte = MetaEvent.new(META_TRACK_END, nil, 0)
  mte.time_from_start = @events.last.time_from_start + mte.delta_time if @events.last
  @events << mte
end

#instrumentObject



49
50
51
# File 'lib/midilib/track.rb', line 49

def instrument
  MetaEvent.bytes_as_str(@instrument)
end

#instrument=(str_or_bytes) ⇒ Object



53
54
55
56
57
58
59
60
# File 'lib/midilib/track.rb', line 53

def instrument=(str_or_bytes)
  @instrument = case str_or_bytes
                when String
                  MetaEvent.str_as_bytes(str_or_bytes)
                else
                  str_or_bytes
                end
end

#merge(event_list) ⇒ Object

Merges an array of events into our event list. After merging, the events’ time_from_start values are correct so you don’t need to worry about calling #recalc_times.



101
102
103
104
# File 'lib/midilib/track.rb', line 101

def merge(event_list)
  @events = merge_event_lists(@events, event_list)
  ensure_track_end_meta_event
end

#merge_event_lists(list1, list2) ⇒ Object

Merges two event arrays together. Does not modify this track.



107
108
109
110
111
112
113
# File 'lib/midilib/track.rb', line 107

def merge_event_lists(list1, list2)
  recalc_times(0, list1)
  recalc_times(0, list2)
  list = list1 + list2
  recalc_delta_from_times(0, list)
  list
end

#nameObject

Return track name. If there is no name, return UNNAMED.



33
34
35
36
# File 'lib/midilib/track.rb', line 33

def name
  event = @events.detect { |e| e.is_a?(MetaEvent) && e.meta_type == META_SEQ_NAME }
  event ? event.data_as_str : UNNAMED
end

#name=(name) ⇒ Object

Set track name. Replaces or creates a name meta-event.



39
40
41
42
43
44
45
46
47
# File 'lib/midilib/track.rb', line 39

def name=(name)
  event = @events.detect { |e| e.is_a?(MetaEvent) && e.meta_type == META_SEQ_NAME }
  if event
    event.data = name
  else
    event = MetaEvent.new(META_SEQ_NAME, name, 0)
    @events[0, 0] = event
  end
end

#quantize(length_or_note) ⇒ Object

Quantize every event. length_or_note is either a length (1 = quarter, 0.25 = sixteenth, 4 = whole note) or a note name (“sixteenth”, “32nd”, “8th triplet”, “dotted quarter”).

Since each event’s time_from_start is modified, we call recalc_delta_from_times after each event quantizes itself.



121
122
123
124
125
126
127
128
129
130
# File 'lib/midilib/track.rb', line 121

def quantize(length_or_note)
  delta = case length_or_note
          when String
            @sequence.note_to_delta(length_or_note)
          else
            @sequence.length_to_delta(length_or_note.to_i)
          end
  @events.each { |event| event.quantize_to(delta) }
  recalc_delta_from_times
end

#recalc_delta_from_times(starting_at = 0, list = @events) ⇒ Object Also known as: sort

The opposite of recalc_times: recalculates delta_time for each event from each event’s time_from_start. This is useful, for example, when merging two event lists. As a side-effect, elements from starting_at are sorted by time_from_start.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/midilib/track.rb', line 146

def recalc_delta_from_times(starting_at = 0, list = @events)
  prev_time_from_start = 0
  # We need to sort the sublist. sublist.sort! does not do what we want.
  # We call mergesort instead of Array.sort because sort is not stable
  # (it can mix up the order of events that have the same start time).
  # See http://wiki.github.com/adamjmurray/cosy/midilib-notes for details.
  list[starting_at..-1] = mergesort(list[starting_at..-1]) do |e1, e2|
    e1.time_from_start <=> e2.time_from_start
  end
  list[starting_at..-1].each do |e|
    e.delta_time = e.time_from_start - prev_time_from_start
    prev_time_from_start = e.time_from_start
  end
end

#recalc_times(starting_at = 0, list = @events) ⇒ Object

Recalculate start times for all events in list from starting_at to end.



134
135
136
137
138
139
140
# File 'lib/midilib/track.rb', line 134

def recalc_times(starting_at = 0, list = @events)
  t = starting_at == 0 ? 0 : list[starting_at - 1].time_from_start
  list[starting_at..-1].each do |e|
    t += e.delta_time
    e.time_from_start = t
  end
end