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