class InevitableCacophony::Parser::Rhythms

Constants

COMBINATION_TYPE_SENTENCE

Used to recognise how multiple rhythms are to be combined

COMPOSITE_RHYTHM_SENTENCE
IS_PRIMARY_COMMENT
POLYRHYTHM_TYPE_SUMMARY
SIMPLE_RHYTHM_SENTENCE

Regular expressions used in parsing

THE_RHYTHM

“the <rhythm>”. Used to match individual components in COMPOSITE_RHYTHM_SENTENCE

Public Instance Methods

parse(form_text) click to toggle source

Parses the rhythms from the given form text.

@param form_text [String] @return [Hash{Symbol,Rhythm}]

# File lib/inevitable_cacophony/parser/rhythms.rb, line 50
def parse(form_text)
        parser = Parser::SectionedText.new(form_text)
        
        simple_rhythms = parse_simple_rhythms(parser)
        composite_rhythms = parse_composite_rhythms(parser, simple_rhythms)
        
        simple_rhythms.merge(composite_rhythms)
end

Private Instance Methods

parse_composite_rhythms(parser, base_rhythms) click to toggle source

@param parser [Parser::SectionedText] @param base_rhythms [Hash{Symbol,Rhythm}] Simpler rhythms that can be used by the composite forms we're parsing. @return [Hash{Symbol,Rhythm}]

# File lib/inevitable_cacophony/parser/rhythms.rb, line 83
def parse_composite_rhythms(parser, base_rhythms)
        
        composite_rhythms = {}
        parser.find_all_paragraphs(COMPOSITE_RHYTHM_SENTENCE).each do |paragraph|
        
                # TODO: write something that handles named matches a bit better
                intro_sentence = paragraph.find(COMPOSITE_RHYTHM_SENTENCE).match(COMPOSITE_RHYTHM_SENTENCE)
                polyrhythm_name = intro_sentence[:name].to_sym
                primary, *secondaries = parse_polyrhythm_components(intro_sentence[:patterns], base_rhythms)
        
                combination_type = paragraph.find(COMBINATION_TYPE_SENTENCE).match(COMBINATION_TYPE_SENTENCE)[:type_summary]
        
                unless combination_type == POLYRHYTHM_TYPE_SUMMARY
                        raise UnrecognisedFormSyntax.new("Unrecognised polyrhythm type #{combination_type}")
                end
        
                composite_rhythms[polyrhythm_name] = Polyrhythm.new(primary, secondaries)
        end
        
        composite_rhythms
end
parse_polyrhythm_components(reference_string, base_rhythms) click to toggle source

@param reference_string [String] The list of rhythms used, like “the anto (considered the primary), the tak, …” @param base_rhythms [Hash{Symbol, Rhythm}] @return [Array<Rhythm>] The matched rhythms, with the primary one as first element/.

# File lib/inevitable_cacophony/parser/rhythms.rb, line 108
def parse_polyrhythm_components(reference_string, base_rhythms)
        primary = nil
        secondaries = []
        
        rhythms_with_comments = reference_string.scan(THE_RHYTHM).map do |rhythm_name, comment|
                component = base_rhythms[rhythm_name.to_sym]
                raise(UnknownBaseRhythm.new(rhythm_name)) unless component
        
                [component, comment]
        end
        
        primary_rhythms = rhythms_with_comments.select { |_, comment| comment == IS_PRIMARY_COMMENT }
        
        if primary_rhythms.length != 1
                raise "Unexpected number of primary rhythms in #{primary_rhythms.inspect}; expected exactly 1"
        end
        primary = primary_rhythms.first.first
        
        remaining_rhythms = rhythms_with_comments - primary_rhythms
        remaining_comments = remaining_rhythms.map(&:last).compact.uniq
        
        if remaining_comments.any?
                raise "Unrecognised rhythm comment(s) #{remaining_comments.inspect}"
        end
        
        secondaries = remaining_rhythms.select {|_, comment| comment.nil? }.map(&:first)
        [primary, *secondaries]
end
parse_simple_rhythms(parser) click to toggle source

@param parser [Parser::SectionedText] @return [Hash{Symbol,Rhythm}]

# File lib/inevitable_cacophony/parser/rhythms.rb, line 63
def parse_simple_rhythms(parser)
        
        rhythms = {}
        
        # Find the rhythm description and the following paragraph with the score.
        parser.sections.each_cons(2) do |rhythm, score|
                match = SIMPLE_RHYTHM_SENTENCE.match(rhythm)
        
                # Make sure we're actually dealing with a rhythm, not some other form element.
                next unless match
        
                rhythms[match[:name].to_sym] = RhythmLine.parse(score)
        end
        
        rhythms
end