GithubHelp home page GithubHelp logo

musicality's Introduction

Build Status Coverage Status

Musicality

The library is based around an abstract representation for music notation. From here, functions are built up to make composing elaborate pieces in this notation representation more manageable. Finally, music performance is supported by providing translation to common formats, like MIDI.

Installation

Add this line to your application's Gemfile:

gem 'musicality'

And then execute:

$ bundle

Or install it yourself as:

$ gem install musicality

Basic Usage

To begin with, Musicality has a class to represent pitch:

require 'musicality'
middle_c = Musicality::Pitch.new(octave: 4, semitone: 0)
puts middle_c.freq # => 261.625...

not_quite_middle_c = Musicality::Pitch.new(octave: 4, semitone: 0, cent: 12)
puts not_quite_middle_c.freq # => 263.445...

For convenience, there are Pitch objects for commonly used octave-semitone combinations:

require 'musicality'
include Musicality::Pitches

c_scale = [C4,D4,E4,F4,G4,A4,B4,C4]
freqs = c_scale.map {|p| p.freq }

Notes can be created like this:

require 'musicality'
include Musicality
include Pitches

# convenience methods for common durations
single = Note.quarter(Ab4)
rest = Note.quarter
chord = Note.whole([C3,E3,G3])

# specific duration + articulation
note = Note.new(Rational(7,8), Bb3, articulation: Articulations::STACCATO)
puts note.to_s  # => "7/8Bb3."

# magic!
puts note.transpose(2).to_s  # => "7/8C4."

# combine notes into a part
part = Part.new(MP, notes:[single,rest,chord])

Or, a compact, string representation can be used, instead.

Part.new(FF, notes: "/4Ab4 /4 1C3,E3,G3".to_notes)

Parts can be put together to make a whole musical score. The block syntax can be used for embedding parts in the score.

require 'musicality'
include Musicality
include Meters
include Dynamics
include Pitches

score = Score::Tempo.new(120, title: "Twinkle, Twinkle, Little Star") do |s|
  s.parts["rhand"] = Part.new(MF) do |p|
    a_notes = q(C4,C4,G4,G4,A4,A4) + h(G4) +
              q(F4,F4,E4,E4,D4,D4) + h(C4)
    b_notes = q(G4,G4,F4,F4,E4,E4) + h(D4)
    p.notes += a_notes + b_notes
  end

  s.parts["lhand"] = Part.new(MF) do |p|
    Cmaj = [C3,E3,G3]
    Fmaj = [F2,A2,C3]
    Gmaj = [G2,B2,D3]

    a_chords = h(Cmaj,Cmaj,Fmaj,Cmaj) +
               h(Fmaj,Cmaj,Gmaj,Cmaj)
    b_chords = h(Cmaj,Fmaj,Cmaj,Gmaj)
    p.notes += a_chords + b_chords
  end

  s.program.push 0...4
  s.program.push 4...6
  s.program.push 4...6
  s.program.push 0...4
end

MIDI Sequencing

A score can be prepared for MIDI playback by converting it to a MIDI::Sequence object (see midilib). This can be accomplished with the ScoreSequencer class or Score#to_midi_seq method. To continue the previous example,

TEMPO_SAMPLE_RATE = 500
seq = twinkle.to_midi_seq TEMPO_SAMPLE_RATE
File.open('twinkle.mid', 'wb'){ |f| seq.write(f) }

LilyPond Engraving

A score can be prepared for engraving (fancy printing) by converting it to a string in LilyPond text format (see lilypond.org). This can be accomplished using the ScoreEngraver class or Score#to_lilypond method. Using the score from the above example,

File.open('twinkle.ly','w'){|f| f.write(twinkle.to_lilypond) }

SuperCollider Rendering

A score can be prepared for rendering (as audio) by converting it to a raw OSC binary file, used for SuperCollider non-realtime rendering (see SuperCollider homepage). This can be accomplished using the SuperCollider::Conductor class or Score#to_osc method. Using the score from the above example,

twinkle.to_osc('twinkle')

Score DSL

The score DSL is an internal DSL (built on Ruby) that consists of a score block with additional blocks inside this to add sections, notes, and tempo/meter/dynamic changes.

Here is an example of a score file.

tempo_score 120 do
  title "Twinkle, Twinkle, Little Star"

  Cmaj = [C3,E3,G3]
  Fmaj = [F2,A2,C3]
  Gmaj = [G2,B2,D3]
  section "A" do
    notes(
      "rhand" => q(C4,C4,G4,G4,A4,A4) + h(G4) +
                 q(F4,F4,E4,E4,D4,D4) + h(C4),
      "lhand" => h(Cmaj,Cmaj,Fmaj,Cmaj) +
                 h(Fmaj,Cmaj,Gmaj,Cmaj)
    )
  end

  section "B" do
    notes(
      "rhand" => q(G4,G4,F4,F4,E4,E4) + h(D4),
      "lhand" => h(Cmaj,Fmaj,Cmaj,Gmaj)
    )
  end
  repeat "B"
  repeat "A"
end

The above score file is processed by the ScoreDSL.load method, as in:

require 'musicality'
include Musicality
include Meters
include Pitches

dsl = ScoreDSL.load 'twinkle.score'
score = dsl.score

Musicality Projects

To create a new project for working on Muscality scores, use the musicality command-line executable that is installed along with the gem.

$ musicality new my_scores

This will create a directory (or fill an existing one) with three files:

  • Gemfile - a Bundler gem dependency file that lists the musicality gem
  • Rakefile - creates rake tasks for processing score files (files with a .score extension)
  • config.yml - customize project configuration options

Also, a scores subdirectory is created as the default location to keep score files.

Before processing any scores, run

$ bundle install

To process score files, run rake with the desired target format. The scores will be converted into any intermediate formats as necessary. For example, to generate a PDF by LilyPond engraving, run

$ rake pdf

This will generate a .pdf file for each score file. In addition, this would cause a chain of intermediate files to be created as well, as follows:

fname.score -> fname.yml -> fname.ly -> fname.pdf

The supported final target formats are listed in the table below.

Target format Rake command
MIDI midi
LilyPond PDF pdf
LilyPond PNG png
LilyPond PostScript ps
SuperCollider AIFF aiff
SuperCollider WAV wav
SuperCollider FLAC flac

In addition, there are also commands for all the intermediate formats

Target format Rake command
YAML yaml
LilyPond (text) ly
Raw OSC (binary) osc

Contributing

  1. Fork it ( https://github.com/[my-github-username]/musicality/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

musicality's People

Contributors

jamestunnell avatar

Stargazers

Rob McKinnon avatar Wesley Newcomb avatar Eleanor Graham avatar Jakub Pavlík avatar

Watchers

David Cole avatar Jakub Pavlík avatar James Cloos avatar  avatar  avatar

musicality's Issues

auditions are kept separate from the score

Currently, part auditions (to choose SuperCollider settings for a part) are kept in a separate file. If the auditions were included in the score, then the score YAML file would contain both the score and audition information. This would allow auditions to be run with just one file input, which is more convenient, especially for the audition rake tasks.

The result would look something like this:

tempo_score(90) do
  supercollider_auditions("bass",0...8) do
    audition("bass2_default", SuperCollider::SynthDefs::BASS2.settings)
    audition("bass2_cutoff500", SuperCollider::SynthDefs::BASS2.settings(:cutoff => 500))
    ...
  end

  supercollider_auditions("lead",0...8) do
    ...
  end

  ...
end

Convenience methods for a string of notes with same duration

When composing it is sometimes necessary to put together a string of notes, each note having the same duration. For example, a string of eighth notes, or a run of sixteenth notes, or a quarter note scale. All these could be made more simply using convenience methods that are specifically for a run of notes where each has a particular duration. For example, a methods for making eighth notes might be called eighth_notes. In general, a function might be provided for notes having an arbitrary duration, maybe called something like notes_with_dur.

Program creation could be slightly more convenient

Creating a program could be as simple as [ 0..2, 1..5 ].to_program. Also Program#initialize could accept one or more segments using a variable-length argument, as in def initialize *segments; end.

Another change that would make a program easier to work with is making Program inherit from Array. Then segments could be available more easily, without calling .segments at all. This would require caution, since the Program needs to be validatable. Either entries need to be checked as they are added, or validation methods need to be added to make sure each element is a Range and is valid.

Notation model could benefit from versioning.

The code is very sensitive to changes in the notation model. Of course the current notation model will at some point need to be solidified before a real initial release can be made. To allow for changes to the notation model, a separate versioning scheme could be implemented, with any changes to the model accompanied by methods for upgrading previous versions of the model to the latest version. Of course the code would always be updated to operate on the latest notation model version.

Composing parts would be easier with built-in offset marking

If offsets could be marked (with a label) in a part or score, then automatic checks could be ran to make sure that all marks with same label occur at the same offset. This would be a nice feature to alert the composer when changes have pushed a mark out of sync with other parts or the score as a whole. Having marks built-in would also make a convenient way for referencing particular offsets for adding dynamic, tempo, or meter changes.

start meter is only used in LilyPond engraving, but is a required parameter for a Score::Tempo object

Since measure durations and offsets were nixed in the Score::Tempo notation model (note duration and offsets only now), the start meter is not needed to be able to perform a score. And since the most common use case for a Score::Tempo object is for performance, it doesn't make much sense to require the start meter parameter. A sensible default value of FOUR_FOUR will suffice. Then if a different start meter is required for engraving, that value can be supplied by a keyword argument.

Add 'musicality update' subcommand

Add feature to be able to update an existing musicality project. This would provide a new Rakefile (override existing) and updated Gemfile (just update musicality version). Also the config.yml could be updated, preserving any defaults that had been overridden.

All this would be triggered by running 'musicality update' on a musicality project directory. This would be accomplished using a new Project::update method.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.