Skip to content

Commit

Permalink
Added MTK::Events::Rest class
Browse files Browse the repository at this point in the history
  • Loading branch information
adamjmurray committed Aug 3, 2013
1 parent 0d2f3d1 commit 4236e62
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 19 deletions.
1 change: 1 addition & 0 deletions lib/mtk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ module Sequencers

require 'mtk/events/event'
require 'mtk/events/note'
require 'mtk/events/rest'
require 'mtk/events/parameter'
require 'mtk/events/timeline'

Expand Down
3 changes: 2 additions & 1 deletion lib/mtk/events/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def self.from_h(hash)
end

def to_h
hash = {:type => @type}
hash = {type: @type}
hash[:value] = @value unless @value.nil?
hash[:duration] = @duration unless @duration.nil?
hash[:number] = @number unless @number.nil?
Expand Down Expand Up @@ -82,6 +82,7 @@ def length
@duration.length
end

# True if this event represents a rest, false otherwise.
# By convention, any events with negative durations are a rest
def rest?
@duration.rest?
Expand Down
1 change: 1 addition & 0 deletions lib/mtk/events/parameter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module MTK

module Events

# A non-note event such as pitch bend, pressure (aftertouch), or control change (CC)
class Parameter < Event

def self.from_midi(status, data1, data2)
Expand Down
84 changes: 84 additions & 0 deletions lib/mtk/events/rest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
module MTK

module Events

# An interval of silence.
#
# By convention, MTK uses {Core::Duration}s with negative values for rests. This class forces the {#duration}
# to always have a negative value.
#
# @note Because a negative durations indicate rests, other {Event} classes may represent rests too.
# Therefore, you should always determine if an {Event} is a rest via the {#rest?} method, instead
# of seeing if the class is an MTK::Events::Rest
class Rest < Event

def initialize(duration, channel=nil)
super :rest, duration:duration, channel:channel
self.duration = @duration # force to be a rest
end

def self.from_h(hash)
new(hash[:duration], hash[:channel])
end

# Assign the duration, forcing to a negative value to indicate this is a rest.
def duration= duration
super
@duration = -@duration unless @duration.rest? # force to be a rest
end

# Rests don't have a corresponding value in MIDI, so this is nil
# @return nil
def midi_value
nil
end

# Rests don't have a corresponding value in MIDI, so this is a no-op
def midi_value= value
end

def to_s
"Rest(#{@duration})"
end

def inspect
"#<#{self.class}:#{object_id} @duration=#{@duration.inspect}" +
if @channel then ", @channel=#{@channel}>" else '>' end
end

end
end


# Construct a {Events::Rest} from a list of any supported type for the arguments: pitch, intensity, duration, channel
def Rest(*anything)
anything = anything.first if anything.size == 1
case anything
when MTK::Events::Rest then anything
when MTK::Events::Event then MTK::Events::Rest.new(anything.duration, anything.channel)
when Numeric then MTK::Events::Rest.new(anything)

when Array
duration = nil
channel = nil
unknowns = []
anything.each do |item|
case item
when MTK::Core::Duration then duration = item
else unknowns << item
end
end

duration = MTK.Duration(unknowns.shift) if duration.nil? and not unknowns.empty?
raise "MTK::Rest() couldn't find a duration in arguments: #{anything.inspect}" if duration.nil?
channel = unknowns.shift.to_i if channel.nil? and not unknowns.empty?

MTK::Events::Rest.new(duration, channel)

else
raise "MTK::Rest() doesn't understand #{anything.class}"
end
end
module_function :Rest

end
26 changes: 10 additions & 16 deletions spec/mtk/events/event_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


describe "#type" do
it "is the first argument passed to AbstractEvent.new" do
it "is the first argument passed to Event.new" do
event.type.should == type
end

Expand All @@ -22,7 +22,7 @@
end

describe "#value" do
it "is the value of the :value key in the options hash passed to AbstractEvent.new" do
it "is the value of the :value key in the options hash passed to Event.new" do
event.value.should == options[:value]
end

Expand All @@ -39,7 +39,7 @@
end

describe "#duration" do
it "is the value of the :duration key in the options hash passed to AbstractEvent.new" do
it "is the value of the :duration key in the options hash passed to Event.new" do
event.duration.should == options[:duration]
end

Expand All @@ -56,7 +56,7 @@
end

describe "#number" do
it "is the value of the :number key in the options hash passed to AbstractEvent.new" do
it "is the value of the :number key in the options hash passed to Event.new" do
event.number.should == options[:number]
end

Expand All @@ -73,7 +73,7 @@
end

describe "#channel" do
it "is the value of the :channel key in the options hash passed to AbstractEvent.new" do
it "is the value of the :channel key in the options hash passed to Event.new" do
event.channel.should == options[:channel]
end

Expand Down Expand Up @@ -155,17 +155,6 @@
end
end

describe "#duration_in_pulses" do
it "multiplies the #length times the argument and rounds to the nearest integer" do
event.duration_in_pulses(111).should == (event.length * 111).round
end

it "is 0 when the #duration is nil" do
event.duration = nil
event.duration_in_pulses(111).should == 0
end
end

describe "from_h" do
it "constructs an Event using a hash" do
EVENT.from_h(hash).should == event
Expand All @@ -187,6 +176,11 @@
EVENT.new(type, :duration => 1.5).duration_in_pulses(59).should == 89
end

it "is 0 when the #duration is nil" do
event.duration = nil
event.duration_in_pulses(111).should == 0
end

it "is always positive (uses absolute value of the duration used to construct the Event)" do
EVENT.new(type, :duration => -1).duration_in_pulses(60).should == 60
end
Expand Down
4 changes: 2 additions & 2 deletions spec/mtk/events/note_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,11 @@
Note(q,mf,C4).should == NOTE.new(C4,q,mf)
end

it "fills in a missing duration type from an number" do
it "fills in a missing duration argument from an number" do
Note(C4,mf,5.25).should == NOTE.new(C4,MTK.Duration(5.25),mf)
end

it '' do
it 'fills in a missing intensity and duration arguments from numbers' do
Note(MTK::Lang::Pitches::C4, MTK::Lang::Intensities::o, 5.25).should == Note(C4, 5.25, 0.75)
end

Expand Down
176 changes: 176 additions & 0 deletions spec/mtk/events/rest_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
require 'spec_helper'

describe MTK::Events::Rest do

REST = MTK::Events::Rest

let(:duration) { -7.5 }
let(:channel) { 3 }
let(:rest) { REST.new duration, channel }
let(:hash) { {type: :rest, duration: duration, channel: channel} }


describe ".new" do
it "requires a duration as the first argument" do
lambda{ REST.new() }.should raise_error
end

it "coerces the duration to an MTK::Core::Duration" do
rest.duration.should be_a MTK::Core::Duration
end

it "forces the duration value negative to be a rest, if needed" do
REST.new(5).duration.should == -5
end
end

describe "#type" do
it "is :rest" do
rest.type.should == :rest
end

it "is a read-only attribute" do
lambda{ rest.type = :anything }.should raise_error
end
end

describe "#duration" do
it "is the value of the first argument to Rest.new, if the value was negative (indicating a rest)" do
rest.duration.should == duration
end
end

describe "#duration=" do
it "sets #duration" do
rest.duration = -42
rest.duration.should == -42
end

it "forces coerces argument to a MTK::Core::Duration" do
rest.duration = 42
rest.duration.should be_a MTK::Core::Duration
end

it "forces the duration value negative to be a rest, if needed" do
rest.duration = 42
rest.duration.should == -42
end
end

describe "#channel" do
it "is the value of the :channel key in the options hash passed to Rest.new" do
rest.channel.should == channel
end

it "defaults to nil" do
REST.new(duration).channel.should be_nil
end
end

describe "#channel=" do
it "sets #channel" do
rest.channel = 12
rest.channel.should == 12
end
end

describe "#midi_value" do
it "is nil" do
rest.midi_value.should be_nil
end
end

describe "#length" do
it "is the absolute value of duration" do
rest.duration = -5
rest.length.should == 5
end
end

describe "#rest?" do
it "is true when the duration is negative" do
rest.rest?.should be_true
end

it "is true event with a positive duration, because the duration was forced negative" do
rest.duration = 5
rest.rest?.should be_true
end
end

describe "from_h" do
it "constructs an Event using a hash" do
REST.from_h(hash).should == rest
end
end

describe "#to_h" do
it "is a hash containing all the attributes of the Rest" do
rest.to_h.should == hash
end
end


describe "#==" do
it "is true when the duration and channel are equal" do
rest.should == REST.new(duration, channel)
end

it "is false when the durations are not equal" do
rest.should_not == REST.new(duration+1, channel)
end

it "is false when the channels are not equal" do
rest.should_not == REST.new(duration, channel+1)
end
end

describe "#to_s" do
it "includes #duration to 2 decimal places" do
REST.new(Duration(-1/3.0)).to_s.should == "Rest(-0.33 beat)"
end
end

describe "#inspect" do
it 'is "#<MTK::Events::Rest:{object_id} @duration={duration.inspect}, @channel={channel}>"' do
rest.inspect.should == "#<MTK::Events::Rest:#{rest.object_id} @duration=#{rest.duration.inspect}, @channel=#{channel}>"
end
end

end


describe MTK do

describe '#Rest' do

it "acts like new for multiple arguments" do
MTK::Rest(4,2).should == REST.new(4,2)
end

it "acts like new for an Array of arguments by unpacking (splatting) them" do
MTK::Rest([4,2]).should == REST.new(4,2)
end

it "returns the argument if it's already a Rest" do
rest = REST.new(4,2)
MTK::Rest(rest).should be_equal(rest)
end

it "makes a Rest of the same duration and channel from other Event types" do
event = MTK::Events::Event.new(:event_type, duration:5, channel:3)
MTK::Rest(event).should == REST.new(5,3)
end

it "raises an error for types it doesn't understand" do
lambda{ MTK::Rest({:not => :compatible}) }.should raise_error
end

it "handles out of order arguments for recognized Duration types" do
MTK::Rest(2,q).should == REST.new(q,2)
end

end

end

0 comments on commit 4236e62

Please sign in to comment.