roborabb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HISTORY.md +5 -0
- data/README.md +61 -0
- data/Rakefile +12 -0
- data/lib/roborabb.rb +16 -0
- data/lib/roborabb/bar.rb +23 -0
- data/lib/roborabb/builder.rb +77 -0
- data/lib/roborabb/core_ext.rb +1 -0
- data/lib/roborabb/lilypond.rb +149 -0
- data/lib/roborabb/version.rb +3 -0
- data/roborabb.gemspec +28 -0
- data/spec/acceptance_spec.rb +22 -0
- data/spec/unit_spec.rb +259 -0
- metadata +82 -0
    
        data/HISTORY.md
    ADDED
    
    
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            Roborabb
         | 
| 2 | 
            +
            ========
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Generates drumming practice charts in [lilypond][lilypond] notation.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            <img
         | 
| 7 | 
            +
              src="https://img.skitch.com/20111210-n7ey6x4jrmiaq11tjj1j8qqd4u.jpg"
         | 
| 8 | 
            +
              alt='example score' />
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            Example
         | 
| 11 | 
            +
            -------
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            Install the gem:
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                gem install roborabb
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Then use it:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                require 'roborabb'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                rock_1 = Roborabb.construct(
         | 
| 22 | 
            +
                  title:          "Rock",
         | 
| 23 | 
            +
                  subdivisions:   8,
         | 
| 24 | 
            +
                  unit:           8,
         | 
| 25 | 
            +
                  time_signature: "4/4",
         | 
| 26 | 
            +
                  notes: {
         | 
| 27 | 
            +
                    hihat: L{|env| true },
         | 
| 28 | 
            +
                    kick:  L{|env| (env.subdivision + 0) % 4 == 0 },
         | 
| 29 | 
            +
                    snare: L{|env| (env.subdivision + 2) % 4 == 0 },
         | 
| 30 | 
            +
                  }
         | 
| 31 | 
            +
                )
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                puts Roborabb::Lilypond.new(rock_1, bars: 16).to_lilypond
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            The resulting file is immediately compilable with [lilypond][lilypond]:
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                ruby examples/rock.rb > rock.ly && lilypond rock.ly # Generates rock.pdf
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            See `examples` directory for more.
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            [lilypond]: http://lilypond.org/
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            Compatibility
         | 
| 44 | 
            +
            -------------
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            Only tested on ruby 1.9.3. Require 1.9, since it uses new style hashes.
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            Developing
         | 
| 49 | 
            +
            ----------
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                git clone git://github.com/xaviershay/roborabb.git
         | 
| 52 | 
            +
                bundle           # Install development dependencies
         | 
| 53 | 
            +
                bundle exec rake # Runs the specs
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            Any big new features require an acceptance test, bug fixes should only require
         | 
| 56 | 
            +
            unit tests. Follow the conventions already present.
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            Status
         | 
| 59 | 
            +
            ------
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            New, but complete.
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/lib/roborabb.rb
    ADDED
    
    | @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            require 'roborabb/version'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'roborabb/core_ext'
         | 
| 4 | 
            +
            require 'roborabb/builder'
         | 
| 5 | 
            +
            require 'roborabb/lilypond'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Roborabb
         | 
| 8 | 
            +
              def construct(plan)
         | 
| 9 | 
            +
                unless plan.has_key?(:notes)
         | 
| 10 | 
            +
                  raise(ArgumentError, "Plan does not contain :notes")
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
                Builder.new(plan)
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
              module_function :construct
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
             | 
    
        data/lib/roborabb/bar.rb
    ADDED
    
    | @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            module Roborabb
         | 
| 2 | 
            +
              class Bar
         | 
| 3 | 
            +
                ATTRIBUTES = [
         | 
| 4 | 
            +
                  :beat_structure,
         | 
| 5 | 
            +
                  :notes,
         | 
| 6 | 
            +
                  :subdivisions,
         | 
| 7 | 
            +
                  :time_signature,
         | 
| 8 | 
            +
                  :title,
         | 
| 9 | 
            +
                  :unit,
         | 
| 10 | 
            +
                ]
         | 
| 11 | 
            +
                attr_reader *ATTRIBUTES
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def initialize(attributes)
         | 
| 14 | 
            +
                  ATTRIBUTES.each do |x|
         | 
| 15 | 
            +
                    send("#{x}=", attributes[x])
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                protected
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                attr_writer *ATTRIBUTES
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            require 'ostruct'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'roborabb/bar'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Roborabb
         | 
| 6 | 
            +
              class Builder
         | 
| 7 | 
            +
                attr_reader :plan
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(plan_hash)
         | 
| 10 | 
            +
                  self.plan       = OpenStruct.new(plan_hash)
         | 
| 11 | 
            +
                  self.bar_env    = OpenStruct.new(index: 0)
         | 
| 12 | 
            +
                  self.enumerator = Enumerator.new do |yielder|
         | 
| 13 | 
            +
                    loop do
         | 
| 14 | 
            +
                      yielder.yield(generate_bar)
         | 
| 15 | 
            +
                      bar_env.index += 1
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def next
         | 
| 21 | 
            +
                  enumerator.next
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                protected
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def generate_bar
         | 
| 27 | 
            +
                  notes = subdivisions.inject(empty_notes) do |notes, subdivision|
         | 
| 28 | 
            +
                    env = build_env(subdivision)
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    plan.notes.map do |name, f|
         | 
| 31 | 
            +
                      notes[name] << resolve(f, env)
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    notes
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  Bar.new(
         | 
| 38 | 
            +
                    subdivisions:   subdivisions.max + 1,
         | 
| 39 | 
            +
                    unit:           resolve(plan.unit, bar_env),
         | 
| 40 | 
            +
                    time_signature: resolve(plan.time_signature, bar_env),
         | 
| 41 | 
            +
                    beat_structure: resolve(plan.beat_structure, bar_env),
         | 
| 42 | 
            +
                    title:          resolve(plan.title, bar_env),
         | 
| 43 | 
            +
                    notes:          notes
         | 
| 44 | 
            +
                  )
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def resolve(f, env)
         | 
| 48 | 
            +
                  if f.respond_to?(:call)
         | 
| 49 | 
            +
                    f.call(env)
         | 
| 50 | 
            +
                  else
         | 
| 51 | 
            +
                    f
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def subdivisions
         | 
| 56 | 
            +
                  (0...resolve(plan.subdivisions, bar_env))
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def empty_notes
         | 
| 60 | 
            +
                  x = plan.notes.keys.map do |name|
         | 
| 61 | 
            +
                    [name, []]
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                  Hash[x]
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def build_env(subdivision)
         | 
| 67 | 
            +
                  OpenStruct.new(
         | 
| 68 | 
            +
                    subdivision: subdivision,
         | 
| 69 | 
            +
                    bar:         bar_env
         | 
| 70 | 
            +
                  )
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                attr_writer :plan
         | 
| 74 | 
            +
                attr_accessor :enumerator
         | 
| 75 | 
            +
                attr_accessor :bar_env
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            alias :L :lambda
         | 
| @@ -0,0 +1,149 @@ | |
| 1 | 
            +
            require 'roborabb/bar'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Roborabb
         | 
| 4 | 
            +
              class Lilypond
         | 
| 5 | 
            +
                def initialize(generator, opts)
         | 
| 6 | 
            +
                  self.generator = generator
         | 
| 7 | 
            +
                  self.opts      = opts
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def to_lilypond
         | 
| 11 | 
            +
                  score = opts[:bars].times.map do
         | 
| 12 | 
            +
                    bar = generator.next
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    format_bar(bar)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  lilypond do
         | 
| 18 | 
            +
                    voice(:up)   { format_bars(score, :upper) } +
         | 
| 19 | 
            +
                    voice(:down) { format_bars(score, :lower) }
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                protected
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                attr_accessor :generator, :opts, :title
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def format_bars(bars, voice)
         | 
| 28 | 
            +
                  last_plan = Bar.new({})
         | 
| 29 | 
            +
                  bars.map do |bar|
         | 
| 30 | 
            +
                    plan = bar[:bar]
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    preamble = ""
         | 
| 33 | 
            +
                    if last_plan.time_signature != plan.time_signature
         | 
| 34 | 
            +
                      preamble += %(\\time #{plan.time_signature}\n)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    if last_plan.beat_structure != plan.beat_structure && plan.beat_structure
         | 
| 38 | 
            +
                      preamble += %(\\set Staff.beatStructure = #'(%s)\n) % [
         | 
| 39 | 
            +
                        plan.beat_structure.join(' ')
         | 
| 40 | 
            +
                      ]
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                    last_plan = plan
         | 
| 43 | 
            +
                    self.title = plan.title
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    preamble + bar[voice]
         | 
| 46 | 
            +
                  end.join(' | ') + ' \\bar "|."'
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def lilypond
         | 
| 50 | 
            +
                  # Evaluating the content first is necessary to infer the title.
         | 
| 51 | 
            +
                  content = yield
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  <<-LP
         | 
| 54 | 
            +
                  \\version "2.14.2"
         | 
| 55 | 
            +
                  \\header {
         | 
| 56 | 
            +
                    title = "#{title}"
         | 
| 57 | 
            +
                    subtitle = " "
         | 
| 58 | 
            +
                  }
         | 
| 59 | 
            +
                  \\new DrumStaff <<
         | 
| 60 | 
            +
                    #{content}
         | 
| 61 | 
            +
                  >>
         | 
| 62 | 
            +
                  LP
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def voice(direction)
         | 
| 66 | 
            +
                  result = <<-LP
         | 
| 67 | 
            +
                  \\new DrumVoice {
         | 
| 68 | 
            +
                    \\override Rest #'direction = ##{direction}
         | 
| 69 | 
            +
                    \\stem#{direction == :up ? "Up" : "Down"}   \\drummode {
         | 
| 70 | 
            +
                      #{yield}
         | 
| 71 | 
            +
                    }
         | 
| 72 | 
            +
                  }
         | 
| 73 | 
            +
                  LP
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def format_bar(bar)
         | 
| 77 | 
            +
                  {
         | 
| 78 | 
            +
                    bar:   bar,
         | 
| 79 | 
            +
                    upper: format_notes(bar, expand(hashslice(bar.notes, :hihat))),
         | 
| 80 | 
            +
                    lower: format_notes(bar, expand(hashslice(bar.notes, :kick, :snare)))
         | 
| 81 | 
            +
                  }
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def format_notes(bar, notes)
         | 
| 85 | 
            +
                  notes.map do |note|
         | 
| 86 | 
            +
                    if note[0].length == 1
         | 
| 87 | 
            +
                      mappings[note[0][0]] + duration(bar, note[1]).to_s
         | 
| 88 | 
            +
                    elsif note[0].length > 1
         | 
| 89 | 
            +
                      "<%s>%s" % [
         | 
| 90 | 
            +
                        note[0].map {|x| mappings[x] }.join(' '),
         | 
| 91 | 
            +
                        duration(bar, note[1])
         | 
| 92 | 
            +
                      ]
         | 
| 93 | 
            +
                    else
         | 
| 94 | 
            +
                      "r%s" % duration(bar, note[1])
         | 
| 95 | 
            +
                    end
         | 
| 96 | 
            +
                  end.join(" ")
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def mappings
         | 
| 100 | 
            +
                  {
         | 
| 101 | 
            +
                    kick:  'bd',
         | 
| 102 | 
            +
                    snare: 'sn',
         | 
| 103 | 
            +
                    hihat: 'hh'
         | 
| 104 | 
            +
                  }
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                def duration(bar, x)
         | 
| 108 | 
            +
                  unit = bar.unit
         | 
| 109 | 
            +
                  [
         | 
| 110 | 
            +
                    unit,
         | 
| 111 | 
            +
                    unit / 2,
         | 
| 112 | 
            +
                    (unit / 2).to_s + ".",
         | 
| 113 | 
            +
                    unit / 4
         | 
| 114 | 
            +
                  ].map(&:to_s)[x-1] || raise("Unsupported duration: #{x}")
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                def hashslice(hash, *keep_keys)
         | 
| 118 | 
            +
                  h = {}
         | 
| 119 | 
            +
                  keep_keys.each do |key|
         | 
| 120 | 
            +
                    h[key] = hash[key] if hash.has_key?(key)
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                  h
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                def expand(notes)
         | 
| 126 | 
            +
                  accum = []
         | 
| 127 | 
            +
                  time  = 0
         | 
| 128 | 
            +
                  out = notes.values.transpose.inject([]) do |out, on_notes|
         | 
| 129 | 
            +
                    on = [*on_notes].map.with_index do |x, i|
         | 
| 130 | 
            +
                      notes.keys[i] if x
         | 
| 131 | 
            +
                    end.compact
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                    if !on.empty? || time >= 4
         | 
| 134 | 
            +
                      if time > 0
         | 
| 135 | 
            +
                        out << [accum, time]
         | 
| 136 | 
            +
                      end
         | 
| 137 | 
            +
                      accum = on
         | 
| 138 | 
            +
                      time = 0
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
                    time += 1
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    out
         | 
| 143 | 
            +
                  end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  out << [accum, time] if time > 0
         | 
| 146 | 
            +
                  out
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
              end
         | 
| 149 | 
            +
            end
         | 
    
        data/roborabb.gemspec
    ADDED
    
    | @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            require File.expand_path('../lib/roborabb/version', __FILE__)
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Gem::Specification.new do |gem|
         | 
| 5 | 
            +
              gem.authors       = ["Xavier Shay"]
         | 
| 6 | 
            +
              gem.email         = ["hello@xaviershay.com"]
         | 
| 7 | 
            +
              gem.description   = %q{Algorithmically generate practice drum scores}
         | 
| 8 | 
            +
              gem.summary       = %q{
         | 
| 9 | 
            +
                Algorithmically generate practice drum scores. Customize algorithms with
         | 
| 10 | 
            +
                ruby with an archaeopteryx-inspired style, output to lilypond format.
         | 
| 11 | 
            +
              }
         | 
| 12 | 
            +
              gem.homepage      = "http://github.com/xaviershay/roborabb"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              gem.executables   = []
         | 
| 15 | 
            +
              gem.files         = Dir.glob("{spec,lib}/**/*.rb") + %w(
         | 
| 16 | 
            +
                                    README.md
         | 
| 17 | 
            +
                                    HISTORY.md
         | 
| 18 | 
            +
                                    Rakefile
         | 
| 19 | 
            +
                                    roborabb.gemspec
         | 
| 20 | 
            +
                                  )
         | 
| 21 | 
            +
              gem.test_files    = Dir.glob("spec/**/*.rb")
         | 
| 22 | 
            +
              gem.name          = "roborabb"
         | 
| 23 | 
            +
              gem.require_paths = ["lib"]
         | 
| 24 | 
            +
              gem.version       = Roborabb::VERSION
         | 
| 25 | 
            +
              gem.has_rdoc      = false
         | 
| 26 | 
            +
              gem.add_development_dependency 'rspec', '~> 2.0'
         | 
| 27 | 
            +
              gem.add_development_dependency 'rake'
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
         | 
| 2 | 
            +
            require 'roborabb'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe 'outputting to lilypond' do
         | 
| 5 | 
            +
              it 'outputs a basic rock beat' do
         | 
| 6 | 
            +
                rabb = Roborabb.construct(
         | 
| 7 | 
            +
                  subdivisions:   8,
         | 
| 8 | 
            +
                  unit:           8,
         | 
| 9 | 
            +
                  time_signature: "4/4",
         | 
| 10 | 
            +
                  notes: {
         | 
| 11 | 
            +
                    hihat: L{|env| true },
         | 
| 12 | 
            +
                    kick:  L{|env| (env.subdivision + 0) % 4 == 0 },
         | 
| 13 | 
            +
                    snare: L{|env| (env.subdivision + 2) % 4 == 0 },
         | 
| 14 | 
            +
                  }
         | 
| 15 | 
            +
                )
         | 
| 16 | 
            +
                ly = Roborabb::Lilypond.new(rabb, bars: 2)
         | 
| 17 | 
            +
                output = ly.to_lilypond.lines.map(&:chomp).join
         | 
| 18 | 
            +
                output.should include('\\time 4/4')
         | 
| 19 | 
            +
                output.should include('hh8 hh8 hh8 hh8 | hh8 hh8 hh8 hh8')
         | 
| 20 | 
            +
                output.should include('bd4 sn4 | bd4 sn4')
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
    
        data/spec/unit_spec.rb
    ADDED
    
    | @@ -0,0 +1,259 @@ | |
| 1 | 
            +
            $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
         | 
| 2 | 
            +
            require 'roborabb'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Roborabb do
         | 
| 5 | 
            +
              def notes(rabb)
         | 
| 6 | 
            +
                rabb.next.notes
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              def default_attributes
         | 
| 10 | 
            +
                {
         | 
| 11 | 
            +
                  subdivisions:   2,
         | 
| 12 | 
            +
                  unit:           8,
         | 
| 13 | 
            +
                  time_signature: '1/4',
         | 
| 14 | 
            +
                  notes:          {}
         | 
| 15 | 
            +
                }
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def construct(attributes)
         | 
| 19 | 
            +
                Roborabb.construct(default_attributes.merge(attributes))
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              describe '#construct' do
         | 
| 23 | 
            +
                it 'raises Argument error when no :notes given' do
         | 
| 24 | 
            +
                  lambda {
         | 
| 25 | 
            +
                    Roborabb.construct(default_attributes.delete_if {|k, _| k == :notes })
         | 
| 26 | 
            +
                  }.should raise_error(ArgumentError)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              describe '#next' do
         | 
| 31 | 
            +
                it 'allows a value for notes' do
         | 
| 32 | 
            +
                  rabb = construct(
         | 
| 33 | 
            +
                    subdivisions: 2,
         | 
| 34 | 
            +
                    notes:        { a: 'A' }
         | 
| 35 | 
            +
                  )
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  notes(rabb).should == { a: ['A', 'A'] }
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                it 'includes subdivision in env yielded to notes' do
         | 
| 41 | 
            +
                  rabb = construct(
         | 
| 42 | 
            +
                    subdivisions: 3,
         | 
| 43 | 
            +
                    notes: {
         | 
| 44 | 
            +
                      a: :subdivision.to_proc
         | 
| 45 | 
            +
                    }
         | 
| 46 | 
            +
                  )
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  notes(rabb).should == { a: [0, 1, 2] }
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                it 'includes bar number in env yielded to config' do
         | 
| 52 | 
            +
                  rabb = construct(
         | 
| 53 | 
            +
                    subdivisions: L{|e| e.index + 1 },
         | 
| 54 | 
            +
                    notes:        { a: 1 }
         | 
| 55 | 
            +
                  )
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  3.times.map { notes(rabb) }.should == [
         | 
| 58 | 
            +
                    { a: [1] },
         | 
| 59 | 
            +
                    { a: [1, 1] },
         | 
| 60 | 
            +
                    { a: [1, 1, 1] }
         | 
| 61 | 
            +
                  ]
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                it 'includes bar number in env yielded to notes' do
         | 
| 65 | 
            +
                  rabb = construct(
         | 
| 66 | 
            +
                    subdivisions: 2,
         | 
| 67 | 
            +
                    notes:        { a: L{|e| e.bar.index } }
         | 
| 68 | 
            +
                  )
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  3.times.map { notes(rabb) }.should == [
         | 
| 71 | 
            +
                    { a: [0, 0] },
         | 
| 72 | 
            +
                    { a: [1, 1] },
         | 
| 73 | 
            +
                    { a: [2, 2] }
         | 
| 74 | 
            +
                  ]
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                it 'includes subdivisons in returned object' do
         | 
| 78 | 
            +
                  rabb = construct(subdivisions: 2)
         | 
| 79 | 
            +
                  rabb.next.subdivisions.should == 2
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                it 'includes unit in returned object' do
         | 
| 83 | 
            +
                  rabb = construct(unit: 8)
         | 
| 84 | 
            +
                  rabb.next.unit.should == 8
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                it 'includes generated unit in returned object' do
         | 
| 88 | 
            +
                  rabb = construct(unit: L{|e| 8 })
         | 
| 89 | 
            +
                  rabb.next.unit.should == 8
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                it 'includes time_signature in returned object' do
         | 
| 93 | 
            +
                  rabb = construct(time_signature: "7/8")
         | 
| 94 | 
            +
                  rabb.next.time_signature.should == "7/8"
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                it 'includes generated time_signature in returned object' do
         | 
| 98 | 
            +
                  rabb = construct(time_signature: L{|e| "7/8" })
         | 
| 99 | 
            +
                  rabb.next.time_signature.should == "7/8"
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                it 'includes beat_structure in returned object' do
         | 
| 103 | 
            +
                  rabb = construct(beat_structure: [3, 2, 2])
         | 
| 104 | 
            +
                  rabb.next.beat_structure.should == [3, 2, 2]
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                it 'includes generated beat_structure in returned object' do
         | 
| 108 | 
            +
                  rabb = construct(beat_structure: L{|e| [3, 2, 2] })
         | 
| 109 | 
            +
                  rabb.next.beat_structure.should == [3, 2, 2]
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                it 'includes title in returned object' do
         | 
| 113 | 
            +
                  rabb = construct(title: "Hello")
         | 
| 114 | 
            +
                  rabb.next.title.should == "Hello"
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                it 'includes generated title in returned object' do
         | 
| 118 | 
            +
                  rabb = construct(title: L{|e| "Hello" })
         | 
| 119 | 
            +
                  rabb.next.title.should == "Hello"
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
              end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            describe Roborabb::Lilypond do
         | 
| 126 | 
            +
              describe '#to_lilypond' do
         | 
| 127 | 
            +
                def bar(attributes = {})
         | 
| 128 | 
            +
                  double("Bar", {
         | 
| 129 | 
            +
                    title:          nil,
         | 
| 130 | 
            +
                    unit:           8,
         | 
| 131 | 
            +
                    notes:          {hihat: [true]},
         | 
| 132 | 
            +
                    time_signature: "4/4",
         | 
| 133 | 
            +
                    beat_structure: [4, 4]
         | 
| 134 | 
            +
                  }.merge(attributes))
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                def output(generator, opts = {bars: 1})
         | 
| 138 | 
            +
                  formatter = described_class.new(generator.each, opts)
         | 
| 139 | 
            +
                  formatter.to_lilypond
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                it 'outputs rests' do
         | 
| 143 | 
            +
                  generator = [bar(notes: {hihat: [false]})]
         | 
| 144 | 
            +
                  output(generator).should include("r")
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                it 'outputs hihats' do
         | 
| 148 | 
            +
                  generator = [bar(notes: {hihat: [true]})]
         | 
| 149 | 
            +
                  output(generator).should include("hh")
         | 
| 150 | 
            +
                end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                it 'calculates durations correctly to a maximum of four units' do
         | 
| 153 | 
            +
                  generator = [bar(unit: 32, notes: {hihat:
         | 
| 154 | 
            +
                    [true] +
         | 
| 155 | 
            +
                    [true] + [false] * 1 +
         | 
| 156 | 
            +
                    [true] + [false] * 2 +
         | 
| 157 | 
            +
                    [true] + [false] * 3 +
         | 
| 158 | 
            +
                    [true] + [false] * 4
         | 
| 159 | 
            +
                  })]
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                  output(generator).should include("hh32 hh16 hh16. hh8 hh8 r32")
         | 
| 162 | 
            +
                end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                it 'outputs kicks and snares' do
         | 
| 165 | 
            +
                  generator = [bar(unit: 4, notes: {
         | 
| 166 | 
            +
                    kick:  [true, false],
         | 
| 167 | 
            +
                    snare: [false, true]
         | 
| 168 | 
            +
                  })]
         | 
| 169 | 
            +
                  output(generator).should include("bd4 sn4")
         | 
| 170 | 
            +
                end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                it 'can output two notes at the same time' do
         | 
| 173 | 
            +
                  generator = [bar(unit: 4, notes: {kick: [true], snare: [true]})]
         | 
| 174 | 
            +
                  output(generator).should include("<bd sn>4")
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                it 'can output a rest before a note' do
         | 
| 178 | 
            +
                  generator = [bar(unit: 8, notes: {hihat: [false, true]})]
         | 
| 179 | 
            +
                  output(generator).should include("r8 hh8")
         | 
| 180 | 
            +
                end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                it 'includes lilypond preamble' do
         | 
| 183 | 
            +
                  lilypond = output([bar])
         | 
| 184 | 
            +
                  lilypond.should include("\\version")
         | 
| 185 | 
            +
                  lilypond.should include("\\new DrumStaff")
         | 
| 186 | 
            +
                end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                it 'places hihats and kick/snare in different voices' do
         | 
| 189 | 
            +
                  generator = [bar(unit: 8, notes: {
         | 
| 190 | 
            +
                    hihat: [true, true],
         | 
| 191 | 
            +
                    kick:  [true, false],
         | 
| 192 | 
            +
                    snare: [false, true]
         | 
| 193 | 
            +
                  })]
         | 
| 194 | 
            +
                  voices = output(generator).split("\\new DrumVoice")[1..-1]
         | 
| 195 | 
            +
                  voices[0].should include("hh8 hh8")
         | 
| 196 | 
            +
                  voices[0].should include("\\override Rest #'direction = #up")
         | 
| 197 | 
            +
                  voices[0].should include("\\stemUp")
         | 
| 198 | 
            +
                  voices[1].should include("bd8 sn8")
         | 
| 199 | 
            +
                  voices[1].should include("\\stemDown")
         | 
| 200 | 
            +
                end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                it 'includes bar lines' do
         | 
| 203 | 
            +
                  generator = [
         | 
| 204 | 
            +
                    bar(notes: {hihat: [true] }),
         | 
| 205 | 
            +
                    bar(notes: {hihat: [false] }),
         | 
| 206 | 
            +
                  ]
         | 
| 207 | 
            +
                  bars = output(generator, bars: 2).split('|')
         | 
| 208 | 
            +
                  bars[0].should include('hh')
         | 
| 209 | 
            +
                  bars[1].should include('r')
         | 
| 210 | 
            +
                end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                it 'includes time signature changes per bar' do
         | 
| 213 | 
            +
                  generator = [
         | 
| 214 | 
            +
                    bar(time_signature: "1/8"),
         | 
| 215 | 
            +
                    bar(time_signature: "1/8"),
         | 
| 216 | 
            +
                    bar(time_signature: "1/4"),
         | 
| 217 | 
            +
                  ]
         | 
| 218 | 
            +
                  bars = output(generator, bars: 3).split('|')
         | 
| 219 | 
            +
                  bars[0].should     include(%(\\time 1/8))
         | 
| 220 | 
            +
                  bars[1].should_not include(%(\\time))
         | 
| 221 | 
            +
                  bars[2].should     include(%(\\time 1/4))
         | 
| 222 | 
            +
                end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                it 'includes beat structure changes per bar' do
         | 
| 225 | 
            +
                  generator = [
         | 
| 226 | 
            +
                    bar(beat_structure: [3, 2]),
         | 
| 227 | 
            +
                    bar(beat_structure: [3, 2]),
         | 
| 228 | 
            +
                    bar(beat_structure: [2, 3]),
         | 
| 229 | 
            +
                  ]
         | 
| 230 | 
            +
                  bars = output(generator, bars: 3).split('|')
         | 
| 231 | 
            +
                  bars[0].should     include(%(\\set Staff.beatStructure = #'(3 2)))
         | 
| 232 | 
            +
                  bars[1].should_not include(%(\\set Staff.beatStructure))
         | 
| 233 | 
            +
                  bars[2].should     include(%(\\set Staff.beatStructure = #'(2 3)))
         | 
| 234 | 
            +
                end
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                it 'does not include beat structure if none provided' do
         | 
| 237 | 
            +
                  generator = [
         | 
| 238 | 
            +
                    bar(beat_structure: [3, 2]),
         | 
| 239 | 
            +
                    bar(beat_structure: nil)
         | 
| 240 | 
            +
                  ]
         | 
| 241 | 
            +
                  bars = output(generator, bars: 2).split('|')
         | 
| 242 | 
            +
                  bars[0].should     include(%(\\set Staff.beatStructure = #'(3 2)))
         | 
| 243 | 
            +
                  bars[1].should_not include(%(\\set Staff.beatStructure))
         | 
| 244 | 
            +
                end
         | 
| 245 | 
            +
             | 
| 246 | 
            +
                it 'includes a final double bar line' do
         | 
| 247 | 
            +
                  output([bar]).should include(' \\bar "|."')
         | 
| 248 | 
            +
                end
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                it "includes the final bar's title as the document title" do
         | 
| 251 | 
            +
                  lilypond = output([
         | 
| 252 | 
            +
                    bar(title: 'Wrong'),
         | 
| 253 | 
            +
                    bar(title: 'Hello'),
         | 
| 254 | 
            +
                  ], bars: 2)
         | 
| 255 | 
            +
                  lilypond.should     include(%(title = "Hello"))
         | 
| 256 | 
            +
                  lilypond.should_not include("Wrong")
         | 
| 257 | 
            +
                end
         | 
| 258 | 
            +
              end
         | 
| 259 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,82 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: roborabb
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
            platform: ruby
         | 
| 7 | 
            +
            authors:
         | 
| 8 | 
            +
            - Xavier Shay
         | 
| 9 | 
            +
            autorequire: 
         | 
| 10 | 
            +
            bindir: bin
         | 
| 11 | 
            +
            cert_chain: []
         | 
| 12 | 
            +
            date: 2011-12-11 00:00:00.000000000 Z
         | 
| 13 | 
            +
            dependencies:
         | 
| 14 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 15 | 
            +
              name: rspec
         | 
| 16 | 
            +
              requirement: &2156657040 !ruby/object:Gem::Requirement
         | 
| 17 | 
            +
                none: false
         | 
| 18 | 
            +
                requirements:
         | 
| 19 | 
            +
                - - ~>
         | 
| 20 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            +
                    version: '2.0'
         | 
| 22 | 
            +
              type: :development
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              version_requirements: *2156657040
         | 
| 25 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 26 | 
            +
              name: rake
         | 
| 27 | 
            +
              requirement: &2156656460 !ruby/object:Gem::Requirement
         | 
| 28 | 
            +
                none: false
         | 
| 29 | 
            +
                requirements:
         | 
| 30 | 
            +
                - - ! '>='
         | 
| 31 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 32 | 
            +
                    version: '0'
         | 
| 33 | 
            +
              type: :development
         | 
| 34 | 
            +
              prerelease: false
         | 
| 35 | 
            +
              version_requirements: *2156656460
         | 
| 36 | 
            +
            description: Algorithmically generate practice drum scores
         | 
| 37 | 
            +
            email:
         | 
| 38 | 
            +
            - hello@xaviershay.com
         | 
| 39 | 
            +
            executables: []
         | 
| 40 | 
            +
            extensions: []
         | 
| 41 | 
            +
            extra_rdoc_files: []
         | 
| 42 | 
            +
            files:
         | 
| 43 | 
            +
            - spec/acceptance_spec.rb
         | 
| 44 | 
            +
            - spec/unit_spec.rb
         | 
| 45 | 
            +
            - lib/roborabb/bar.rb
         | 
| 46 | 
            +
            - lib/roborabb/builder.rb
         | 
| 47 | 
            +
            - lib/roborabb/core_ext.rb
         | 
| 48 | 
            +
            - lib/roborabb/lilypond.rb
         | 
| 49 | 
            +
            - lib/roborabb/version.rb
         | 
| 50 | 
            +
            - lib/roborabb.rb
         | 
| 51 | 
            +
            - README.md
         | 
| 52 | 
            +
            - HISTORY.md
         | 
| 53 | 
            +
            - Rakefile
         | 
| 54 | 
            +
            - roborabb.gemspec
         | 
| 55 | 
            +
            homepage: http://github.com/xaviershay/roborabb
         | 
| 56 | 
            +
            licenses: []
         | 
| 57 | 
            +
            post_install_message: 
         | 
| 58 | 
            +
            rdoc_options: []
         | 
| 59 | 
            +
            require_paths:
         | 
| 60 | 
            +
            - lib
         | 
| 61 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 62 | 
            +
              none: false
         | 
| 63 | 
            +
              requirements:
         | 
| 64 | 
            +
              - - ! '>='
         | 
| 65 | 
            +
                - !ruby/object:Gem::Version
         | 
| 66 | 
            +
                  version: '0'
         | 
| 67 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 68 | 
            +
              none: false
         | 
| 69 | 
            +
              requirements:
         | 
| 70 | 
            +
              - - ! '>='
         | 
| 71 | 
            +
                - !ruby/object:Gem::Version
         | 
| 72 | 
            +
                  version: '0'
         | 
| 73 | 
            +
            requirements: []
         | 
| 74 | 
            +
            rubyforge_project: 
         | 
| 75 | 
            +
            rubygems_version: 1.8.6
         | 
| 76 | 
            +
            signing_key: 
         | 
| 77 | 
            +
            specification_version: 3
         | 
| 78 | 
            +
            summary: Algorithmically generate practice drum scores. Customize algorithms with
         | 
| 79 | 
            +
              ruby with an archaeopteryx-inspired style, output to lilypond format.
         | 
| 80 | 
            +
            test_files:
         | 
| 81 | 
            +
            - spec/acceptance_spec.rb
         | 
| 82 | 
            +
            - spec/unit_spec.rb
         |