lib/alda-rb.rb in alda-rb-0.1.0 vs lib/alda-rb.rb in alda-rb-0.1.2

- old
+ new

@@ -5,11 +5,11 @@ "[#{map(&:to_alda_code).join ' '}]" end end class Hash def to_alda_code - "{#{to_a.flatten.map(&:to_alda_code).join ' '}}" + "{#{to_a.reduce(:+).map(&:to_alda_code).join ' '}}" end end class String def to_alda_code inspect @@ -23,10 +23,15 @@ class Numeric def to_alda_code inspect end end +class Range + def to_alda_code + "#{first}-#{last}" + end +end class Proc # Runs +self+ for +n+ times. def * n if !lambda? || arity == 1 n.times &self @@ -46,23 +51,27 @@ singleton_class.attr_accessor :executable @executable = 'alda' # The method give Alda# ability to invoke +alda+ at the command line, # using +name+ as subcommand and +args+ as arguments. + # +opts+ are converted to command line options. # # The return value is the string output by the command in STDOUT. # # If the exit code is nonzero, a CommandLineError# is raised. # @example # Alda.version # # => "Client version: 1.4.0\nServer version: [27713] 1.4.0\n" - # Alda.parse '-c', 'bassoon: o3 c' + # Alda.parse code: 'bassoon: o3 c' # # => "{\"chord-mode\":false,\"current-instruments\":...}\n" # Alda.sandwich # # => Alda::CommandLineError (Expected a command, got sandwich) - def self.method_missing name, *args + def self.method_missing name, *args, **opts name = name.to_s.gsub ?_, ?- + args.concat opts.map { |key, val| + ["--#{key.to_s.gsub ?_, ?-}", val.to_s] + }.flatten output = IO.popen [executable, name, *args], &:read raise CommandLineError.new $?, output if $?.exitstatus.nonzero? output end @@ -92,11 +101,11 @@ @status = status end end # Including this module can make your class have the ability - # to have a event list. + # to have an event list. # See docs below to get an overview of its functions. module EventList # The array containing the events (Event# objects), # most of which are EventContainer# objects. @@ -118,24 +127,26 @@ # # 4. Starting with one of "a", "b", ..., "g": note. See Note#. # # 5. Starting with "r": rest. See Rest#. # - # 6. Starting with "x": chord. See Chord#. + # 6. "x": chord. See Chord#. # - # 7. Starting with "o": octave. See Octave#. + # 7. "s": sequence. See Sequence#. # - # 8. Starting with "v": voice. See Voice#. + # 8. Starting with "o": octave. See Octave#. # - # 9. Starting with "__" (2 underlines): at marker. See AtMarker#. + # 9. Starting with "v": voice. See Voice#. # - # 10. Starting with "_" (underline): marker. See Marker#. + # 10. Starting with "__" (2 underlines): at marker. See AtMarker#. # + # 11. Starting with "_" (underline): marker. See Marker#. + # # Notes cannot have dots. # To tie multiple durations, +_+ is used instead of +~+. # - # All the appended events are contained in a EventContainer# object, + # All the appended events are contained in an EventContainer# object, # which is to be returned. # # These sugars forms a DSL. # @see #initialize. # @return an EventContainer# object. @@ -151,10 +162,12 @@ Note.new pitch, duration when /^r(?<duration>.*)$/ =~ name Rest.new duration when /^x$/ =~ name Chord.new &block + when /^s$/ =~ name + Sequence.new *args, &block when /^o(?<num>\d*)$/ =~ name Octave.new num when /^v(?<num>\d+)$/ =~ name Voice.new num when /^__(?<head>.+)$/ =~ name @@ -169,12 +182,13 @@ end # Append the events of another EventList# object here. # This method covers the disadvantage of alda's being unable to # import scores from other files. + # See https://github.com/alda-lang/alda-core/issues/8. def import event_list - @events += event_list.events + @events.concat event_list.events end # @param block to be passed with the EventList# object as +self+. # @example # Alda::Score.new do @@ -202,35 +216,59 @@ def events_alda_codes delimiter = ' ' @events.map(&:to_alda_code).join delimiter end end - # The class mixes in EventList# and provides a method to play. + # The class mixes in EventList# and provides methods to play or parse. class Score include EventList # Plays the score. - # @return the command line output of the +alda+ command. + # @return The command line output of the +alda+ command. # @example # Alda::Score.new { piano_; c; d; e }.play # # => "[27713] Parsing/evaluating...\n[27713] Playing...\n" # # (and plays the sound) - def play + # Alda::Score.new { piano_; c; d; e }.play from: 1 + # # (plays only an E note) + def play **opts Alda.stop - Alda.play '--code', events_alda_codes + Alda.play code: self, **opts end + + # Parses the score. + # @return The JSON string of the parse result. + # @example + # Alda::Score.new { piano_; c }.parse output: :events + # # => "[{\"event-type\":...}]\n" + def parse **opts + Alda.parse code: self, **opts + end + + # Exports the score. + # @return The command line output of the +alda+ command. + # @example + # Alda::Score.new { piano_; c }.export output: 'temp.mid' + # # (outputs a midi file called temp.mid) + def export **opts + Alda.export code: self, **opts + end + + def to_s + events_alda_codes + end end # The class of elements of EventList#events. class Event # The EventList# object that contains it. - # Note that it may not be directly contained, but with a EventContainer# + # Note that it may not be directly contained, but with an EventContainer# # object in the middle. attr_accessor :parent - # The callback invoked when it is contained in a EventContainer#. + # The callback invoked when it is contained in an EventContainer#. # It is overridden in InlineLisp#, so be aware if you want to # override InlineLisp#on_contained. # @example # class Alda::Note # def on_contained @@ -251,16 +289,23 @@ class EventContainer < Event # The contained Event# object. attr_accessor :event + # The repetition counts. +nil+ if none. + attr_accessor :count + + # The repetition labels. Empty if none. + attr_accessor :labels + # @param event The Event# object to be contained. # @param parent The EventList# object containing the event. def initialize event, parent @event = event @parent = parent @event.parent = @parent + @labels = [] @event.on_contained end # Make #event a Chord# object. # @example @@ -282,15 +327,33 @@ end self end def to_alda_code - @event.to_alda_code + result = @event.to_alda_code + unless @labels.empty? + result.concat ?', @labels.map(&:to_alda_code).join(?,) + end + result.concat ?*, @count.to_alda_code if @count + result end + # Marks repetition. + def * num + @count = num + end + + # Marks alternative repetition. + def % labels + labels = [labels] unless labels.respond_to? :to_a + @labels.replace labels.to_a + end + def method_missing name, *args - @event.__send__ name, *args + result = @event.__send__ name, *args + result = self if result == @event + result end end # Inline lisp event. class InlineLisp < Event @@ -339,34 +402,36 @@ # Append a sharp sign after #pitch. # @example # Alda::Score.new { piano_; +c }.play # # (plays a C\# note) def +@ - @pitch += ?+ + @pitch.concat ?+ self end # Append a flat sign after #pitch. # @example # Alda::Score.new { piano_; -d }.play # # (plays a Db note) def -@ - @pitch += ?- + @pitch.concat ?- self end # Append a natural sign after #pitch # @example # Alda::Score.new { piano_; key_sig 'f+'; ~f }.play # # (plays a F note) def ~ - @pitch += ?_ + @pitch.concat ?_ self end def to_alda_code - @pitch + @duration + result = @pitch + @duration + result.concat ?*, @count.to_alda_code if @count + result end end # A rest event. class Rest < Event @@ -464,13 +529,25 @@ @arg = arg end def to_alda_code result = @names.join ?/ - result += " \"#{@arg}\"" if @arg - result + ?: + result.concat " \"#{@arg}\"" if @arg + result.concat ?: end + + # @example + # Alda::Score.new do + # violin_/viola_/cello_('strings'); g1_1_1 + # strings_.cello_; -o; c1_1_1 + # end.play + def method_missing name, *args + name = name.to_s + return super unless name[-1] == ?_ + name[-1] = '' + @names.last.concat ?., name + end end # A voice event. class Voice < Event @@ -523,11 +600,11 @@ def to_alda_code ?% + @name end end - # An at-marker event + # An at-marker event. # @see Marker# class AtMarker < Event # The corresponding marker's name attr_accessor :name @@ -537,8 +614,17 @@ @name = name.to_s.gsub ?_, ?- end def to_alda_code ?@ + @name + end + end + + # A sequence event. Includes EventList#. + class Sequence < Event + include EventList + + def to_alda_code + @events.to_alda_code end end end