module Poefy::PoeticFormFromText
Public Instance Methods
poetic_form_from_text(lines)
click to toggle source
Read a song lyric file, output a poetic_form that matches its form.
# File lib/poefy/poetic_form_from_text.rb, line 13 def poetic_form_from_text lines # If lines is not an array, assume string and split on newlines. lines = lines.respond_to?(:each) ? lines : lines.split("\n") # Remove duplicate '' elements that are neighbours in the array. # https://genius.com/The-monkees-im-a-believer-lyrics prev_line = '' lines.map! do |i| out = (i == '' && prev_line == '') ? nil : i prev_line = i out end lines.compact! # For refrains, we don't care about the lines exactly, just # the structure. So we can delete punctuation and downcase. lines = lines.map do |line| hash = {} hash[:orig] = line hash[:strip] = line.strip hash[:downcase] = line.strip.gsub(/[[:punct:]]/, '').downcase hash end # Find all the lines that are duplicated. # These will be the refrain lines. refrains = lines.map { |i| i[:downcase] } refrains = refrains.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h } refrains = refrains.select { |k, v| v > 1 && k != '' }.keys # Give each a unique refrain ID. buffer = {} refrains.each.with_index { |line, id| buffer[line] = id } refrains = buffer # Loop through and describe each line. lines = lines.map.with_index do |line, index| hash = {} # Text of the line. hash[:strip] = line[:strip] hash[:downcase] = line[:downcase] # Get the phrase info for the line. phrase = phrase_info line[:strip] # Misc details. hash[:num] = index + 1 hash[:syllable] = phrase[:syllables] hash[:last_word] = phrase[:last_word] hash[:indent] = (line[:orig].length - line[:orig].lstrip.length) / 2 # The rhyme tag array for the line. hash[:rhyme_tags] = phrase[:rhymes] # Map [:refrain] and [:exact]. # (They are mutually exclusive) # If it needs to be an exact line, we don't need rhyme tokens. if bracketed?(line[:strip]) hash[:exact] = line[:strip] hash[:rhyme_letter] = nil hash[:syllable] = 0 elsif refrains.keys.include?(line[:downcase]) hash[:refrain] = refrains[line[:downcase]] end hash end # [:rhyme_tags] may well contain more than one rhyme tag. # e.g. 'wind' rhymes with 'sinned' and 'find'. # So we will compare this array against the rhymes of each # other line in the array, to find the correct one to use. # We will work from the closest lines, until we find a match. lines.each.with_index do |line, index| # Compare each other rhyme tag, order by closeness. found_rhyme = line[:rhyme_tags].first if line[:rhyme_tags].length > 1 lines.sort_by_distance_from_index(index).each do |i| i[:rhyme_tags].each do |tag| if line[:rhyme_tags].include?(tag) found_rhyme = tag break end end end end # If we haven't found the rhyme, then it doesn't matter, # just use the first in the tag array. lines[index][:rhyme_tags] = *found_rhyme lines[index][:rhyme_tag] = found_rhyme lines[index][:rhyme_letter] = found_rhyme end # Split into separate sections, [:rhyme] and [:syllable]. rhyme = lines.map do |line| hash = {} hash[:token] = line[:rhyme_letter] || ' ' hash[:rhyme_letter] = hash[:token] hash[:refrain] = line[:refrain] if line[:refrain] hash[:exact] = line[:exact] if line[:exact] hash end syllable = {} lines.map.with_index do |line, index| syllable[index+1] = line[:syllable] if line[:syllable] > 0 end # Has to be a single character, so 9 is the maximum. indent = lines.map do |line| line[:indent] >= 9 ? 9 : line[:indent] end.join poetic_form = { rhyme: rhyme, syllable: syllable, indent: indent } poetic_form end