Class: Webmidi::SMF::Track

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

Defined Under Namespace

Classes: NoteSpan

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name: nil, channel: nil) ⇒ Track

Returns a new instance of Track.



12
13
14
15
16
# File 'lib/webmidi/smf/track.rb', line 12

def initialize(name: nil, channel: nil)
  @name = name
  @channel = channel
  @events = []
end

Instance Attribute Details

#channelObject

Returns the value of attribute channel.



10
11
12
# File 'lib/webmidi/smf/track.rb', line 10

def channel
  @channel
end

#nameObject

Returns the value of attribute name.



10
11
12
# File 'lib/webmidi/smf/track.rb', line 10

def name
  @name
end

Instance Method Details

#<<(event) ⇒ Object



27
28
29
# File 'lib/webmidi/smf/track.rb', line 27

def <<(event)
  add_event(event)
end

#add_event(event) ⇒ Object



22
23
24
25
# File 'lib/webmidi/smf/track.rb', line 22

def add_event(event)
  @events << event
  self
end

#control_changesObject



76
77
78
# File 'lib/webmidi/smf/track.rb', line 76

def control_changes
  @events.lazy.select { |e| e.is_a?(MIDIEvent) && e.message.is_a?(Message::Channel::ControlChange) }
end

#each(&block) ⇒ Object



31
32
33
# File 'lib/webmidi/smf/track.rb', line 31

def each(&block)
  @events.each(&block)
end

#eventsObject



18
19
20
# File 'lib/webmidi/smf/track.rb', line 18

def events
  @events.dup
end

#note_spansObject



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/webmidi/smf/track.rb', line 43

def note_spans
  active = Hash.new { |hash, key| hash[key] = [] }
  spans = []
  use_delta_time = @events.all? { |event| event.absolute_time.zero? }
  tick = 0

  @events.each do |event|
    tick += event.delta_time
    next unless event.is_a?(MIDIEvent) && note_event?(event.message)

    time = use_delta_time ? tick : event.absolute_time
    message = event.message
    key = [message.channel, message.note]

    if message.is_a?(Message::Channel::NoteOn) && message.velocity.positive?
      active[key] << [event, time]
    elsif (started = active[key].shift)
      start_event, start_time = started
      spans << NoteSpan.new(
        note: message.note,
        channel: message.channel,
        start_time: start_time,
        end_time: time,
        duration: time - start_time,
        note_on: start_event,
        note_off: event
      )
    end
  end

  spans
end

#notesObject



39
40
41
# File 'lib/webmidi/smf/track.rb', line 39

def notes
  @events.lazy.select { |e| e.is_a?(MIDIEvent) && note_event?(e.message) }
end

#quantize!(grid) ⇒ Object

Raises:



114
115
116
117
118
119
120
121
# File 'lib/webmidi/smf/track.rb', line 114

def quantize!(grid)
  raise InvalidSMFError, "Quantize grid must be a positive integer, got #{grid.inspect}" unless grid.is_a?(Integer) && grid.positive?

  @events.each do |event|
    event.absolute_time = ((event.absolute_time.to_f / grid).round * grid).to_i
  end
  recalculate_delta_times!
end

#recalculate_delta_times!Object



104
105
106
107
108
109
110
111
112
# File 'lib/webmidi/smf/track.rb', line 104

def recalculate_delta_times!
  sort_by_absolute_time!
  previous = 0
  @events.each do |event|
    event.delta_time = event.absolute_time - previous
    previous = event.absolute_time
  end
  self
end

#sizeObject



35
36
37
# File 'lib/webmidi/smf/track.rb', line 35

def size
  @events.size
end

#sort_by_absolute_time!Object



99
100
101
102
# File 'lib/webmidi/smf/track.rb', line 99

def sort_by_absolute_time!
  @events.sort_by!(&:absolute_time)
  self
end

#tempo_changesObject



80
81
82
# File 'lib/webmidi/smf/track.rb', line 80

def tempo_changes
  @events.lazy.select { |e| e.is_a?(MetaEvent) && e.type == MetaEvent::META_TYPES[:tempo] }
end

#transpose(semitones) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/webmidi/smf/track.rb', line 84

def transpose(semitones)
  new_track = Track.new(name: @name, channel: @channel)
  @events.each do |event|
    if event.is_a?(MIDIEvent) && note_event?(event.message)
      msg = event.message
      new_note = (msg.note + semitones).clamp(0, 127)
      new_msg = msg.with(note: new_note)
      new_track << MIDIEvent.new(message: new_msg, delta_time: event.delta_time, absolute_time: event.absolute_time)
    else
      new_track << event
    end
  end
  new_track
end