ianring/php-music-tools

This package is abandoned and no longer maintained. No replacement package was suggested.

A PHP library for music analysis, construction, generation, and more

dev-master 2020-01-09 16:03 UTC

This package is auto-updated.

Last update: 2020-01-28 04:03:50 UTC


README

A PHP library for music analysis, generation, manipulation, and more

This project is a work in progress. Features described here may not exist or may not work, and there may be features that work that aren't documented here yet. This is a public, open-source project so if you see something that isn't right, be a hero: fork it and fix it!

The class structure in PHPMusicTools (PMT) is modelled closely after the markup of MusicXML, and was derived from an older project named PHPMusicXML. One goal of this project is to be able to manipulate musical language programmatically, and export the result as MusicXML, for easy rendering by a 3rd party typesetting tool. In addition to MusicXML, the project also integrates in certain ways to output for LilyPond, MIDI, SVG, CSV, etc.

Basic Usage

Create a file in your project named composer.json, and put this in it:

{
    "require": {
        "ianring/PHPMusicTools": "dev-master"
    }
}

Then install composer, using this command:

curl -sS https://getcomposer.org/installer | php

Then execute this command:

php composer.phar install

Now you may use PHPMusicTools Classes in your project. For example:

<?php
use ianring\PHPMusicTools;
require 'vendor/autoload.php';

$score = new Score();
// ... lots of things here
echo $score->toXML();

Object construction

Every class in PHPMusicTools has two ways to create an instance. The first is to use the "new" keyword, providing its properties as arguments, like this:

$pitch = new Pitch('C', -1, 4);

Each class also has a static constructFromArray() method, which accepts a PHP array for its properties.

$pitch = Pitch::constructFromArray(array(
	'step' => 'C',
	'alter' => 1,
	'octave' => 4,
))

The constructFromArray method is recursive, so if your chord array contains a note array, and your note arrays contain a pitch array, they will be transformed into Chord and Note and Pitch objects. This is a convenient way to serialize a complex musical structure into things that are enumerable and fun to manipulate.

$chord = Chord::constructFromArray(
	array(
		'notes' => array(
			0 => array(
				'pitch' => array(
					'step' => 'C',
					'alter' => 1,
					'octave' => 4,
				),
				'rest' => false,
			)
		)
	)
);

Some object properties are singular, but some properties are plural, like the notes in a Chord or the measures in a Part. In the object construction these are named with their plural nouns, e.g. "notes" is an array of Note objects, "measures" is the array of Measure objects, and so on.

Classes

Pitch

A pitch object has three properties: step, alter, and octave. These are directly mapped to the MusicXML elements that represent the notation of a pitch, so there is a difference between B flat and A sharp.

You can create a pitch like this:

$pitch = new Pitch('C', -1, 4);

You can also use this shorthand:

$p = new Pitch('C-4');

The tools will also accept Lilypond "absolute" style:

$p = new Pitch('cis,,');

Pitches are cool because you can do transposition on them:

$p->transpose(6); // transposes the pitch up 6 semitones
$p->transpose(-3); // transposes the pitch down 3 semitones

A transposition might result in an altered note, e.g. if $pitch is 'C4', $pitch->transpose(-4) will result in either a G sharp or an A flat. To resolve this ambiguity, transpose() accepts a second argument for its preferred alteration. If omitted, the alteration will be the same as the original note if there is one.

$p->transpose(6, 1); // will create an F sharp.
$p->transpose(6, -1); // will create a G flat.

In MusicXML, pitch is described using "step", "alter", and "octave", whereas in music analysis, pitch is often described using "chroma" and "height". Chroma is the chromatic position or pitch class in the 12-tone scale, for which we have names like "C sharp". Whereas music notation cares about "step" and "alter" to know the difference between the visual representation of a B flat or A sharp, chroma doesn't carry information about notation, and can be represented as a number from 0 to 12. "Height" is the octave in which the chroma resides. Two pitches can have the same chroma in different heights (e.g. C#4 and C#5), and notes can of course have the same height and different chromas (e.g. D4 and F4).

It's important to understand that the octave property of a pitch is literally related to its height, and is not related to its step or its spelling. For example, when we alter C4 (middle C) by adding a flat, the result is C-3, not C-4. Likewise, B+4 is im octave 4, and is enharmonic with middle C, not the C above it.

The chroma is a number from 0 to 11. 0 is the chroma of C natural, and 11 is the chroma of B.

$pitch = new Pitch('F', 1, 3);
echo $pitch->chroma(); // 6, aka F sharp
$pitch->transpose(-4);
echo $pitch->chroma(); // 2, aka D natural

Heightless pitches

A Pitch might be "heightless", when it represents a Chroma with no "octave" property. Heightlessness is a useful concept in music analysis, because it allows you to examine the use of chromas without regard for their octave. Transposing a heightless pitch will result in another heightless pitch. Heightless pitches can be assigned to Notes (which would be a weird thing to do, but it is possible), but they can not be rendered as XML, so if you're mixing heightless pitches into music that is being rendered as XML, be careful.

Note

A Note has many properties; the object constructor is a little crazy

$note = new Note(
	$pitch = new Pitch(),
	$rest = false,
	$duration = 4,
	$voice = 1,
	$type = 'quarter',
	$accidental = 'flat',
	$dot = false,
	$tie = false,
	$timeModification = new TimeModification(),
	$beams = new NoteBeam(),
	$defaultX = null,
	$defaultY = null,
	$chord = false,
	$notations = array(),
	$articulations = array(),
	$staff = 1
);

It's actually easier and more sensible to use the constructFromArray method, because you can pass in only the properties that aren't default:

$note = Note::constructFromArray(
	array(
		'pitch' => array(
			'step' => 'A',
			'alter' => -1,
			'octave' => 3,
		),
		'duration' => 8
	)
)

Here is the complete construction of a Note with everything provided:

$note = Note::constructFromArray(
	array(
		'pitch' => array( // becomes a Pitch object
			'step' => 'C',
			'alter' => 1,
			'octave' => 4,
		),
		'rest' => false,
		'duration' => 4,
		'voice' => 1,
		'type' => 'eighth',
		'accidental' => 'sharp',
		'dot' => false,
		'tie' => true,
		'timeModification' => array( // becomes a TimeModification object
			'actualNotes' => 3,
			'normalNotes' => 2,
		),
		'defaultX' => 3,
		'defaultY' => 1,
		'chord' => true,
		'notations' => array(
			array( // becomes a Tuplet object
				'notationType' => 'tuplet',
				'number' => 1,
				'type' => 'stop',
			),
			array( // becomes a Slur object
				'notationType' => 'slur',
				'type' => 'start',
				'number' => 1,
			)
		),
		'articulations' => array(
			array( // becomes an Articulation object
				'articulationType' => 'accent',
				'defaultX' => -1,
				'defaultY' => -71,
				'placement' => 'below',
			),
		),
		'staff' => 1,
		'beams' => array(
			array( // becomes a NoteBeam object
				'number' => 1,
				'type' => 'begin',
			),
			array( // becomes another NoteBeam object
				'number' => 2,
				'type' => 'begin',
			)
		),
		'stem' => array( // becomes a NoteStem object
			'defaultX' => 2,
			'defaultY' => 3,
			'direction' => 'up',
		)
	)
);

A note can be transposed too.

$note->transpose(5, -1); // transpose up 5 semitones, preferring flats.

Chord

A chord is a group of notes that sound simultaneously. Its only properties is $notes, an array of Note objects.

$chord = new Chord(array($note1, $note2, $note3));

$chord = Chord::constructFromArray(
	array(
		'notes' => array($note1, $note2, $note3)
	)
);


$chord->addNote($note4);

Chords may be transposed:

$chord->transpose(5, -1); // transpose up 5 semitones, preferring flats.

Layer

A layer allows multiple sequences of chords to share a single measure. This is common for notation of counterpoint, and for polyphonic instruments like the piano. Layers have no special properties of their own.

$layer = new Layer();

You can add a chord, or a note.

$layer->addChord($chord);
$layer->addNote($note);

You can add a Note directly to a Layer, but internally it's creating a Chord with one Note in it, and adding that.

Layers may be transposed:

$layer->transpose(5, -1); // transpose up 5 semitones, preferring flats.

Measure

A measure is a collection of layers, together describing one discrete duration of music defined by the time signature.

$measure = new Measure($properties);
$measure->addLayer($layer);

A measure has many properties:

number
the sequential number of this measure, starting from 1
<dt>division</dt>
<dd>the number of divisions of a single beat. If the time signature is 4/4, then one beat is a quarter note. If your measure contains any sixteenth notes, you will need the division to be at least 4. If you also use eighth-note triplets, then the division must be 12. The number of "ticks" (discrete equal time durations, as in a ticking clock) in a measure is beats * divisions, and each note in your measure must be assigned to one of the ticks.</dd>

<dt>key</dt>
<dd>Must be a Key object, as described below</dd>

<dt>time</dt>
<dd>has properties "beats", and "beat-type", and optionally "symbol".</dd>

<dt>clef</dt>
<dd>must be a Clef object, as described below</dd>

<dt>barlines</dt>
<dd>
	may be a single barline or an array of barlines, if the measure has multiple staves.	
	each must be a Barline object, as described below
</dd>

<dt>implicit</dt>
<dd>boolean. Defaults to false if omitted. If true, then the measure won't be counted with a measure number, as in pickup measures and the last half of mid-measure repeats.</dd>

<dt>non-controlling</dt>
<dd>boolean, defaults to false. If true, the left barline of this measure does not coincide with the left barline of measures in other parts.</dd>

<dt>width</dt>
<dd>to explicitly set the width of a measure</dd>

Key

The Key class builds a Key, to use as the 'key' property in your Measure. It is represented using the properties "fifths" and "mode". In major mode, C major is 0 fifths, G is 1, D is 2... F is -1, B flat is -2. The class provides a shorthand for building a key, by constructing it with a string (like "F minor") instead of an array.

These three ways of creating $key all accomplish the same thing:

$key = new Key(3, 'major');

$key = Key::constructFromArray(
	array(
		'fifths' => 3,
		'mode' => 'major'
	)
);

$key = Key::constructFromString('D major'));

Clef

There is a Clef class which provides a shorthand for creating common clefs. The clef has two properties: sign and line.

// this creates a treble clef
$clef = new Clef(array('sign' => 'G', 'line' => 4));

$clef = Clef::constructFromArray(
	array(
		'sign' => 'G',
		'line' => 4
	)
);

$clef = Clef::constructFromString('treble');

Staves

A measure may contain multiple staves. Each of these has its own clef. The number of staves is controlled by the number of clefs. To override the number of staves for complex notation situations, put a number in the "staves" property.

// This measure will have two staves, without having to set the "staves" property
$measure->setProperty('clef', array(
	new Clef('treble'),
	new Clef('bass')
));

// explicitly use a different number of staves
$measure->setProperty('staves', 3);

Barline

The Barline class has four main properties: "location", "bar-style", "repeat", and "ending". "repeat" has sub-properties "direction" and "winged". "ending" has sub-properties "type" and "number".

$barline = Barline::constructFromArray(
	array(
		'location' => 'right',
		'bar-style' => 'heavy-light',
		'repeat' => array( // this becomes a BarlineRepeat object
			'direction' => 'backward',
			'winged' => 'none'
		),
		'ending' => array( // this becomes a BarlineEnding object
			'type' => 'stop',
			'number' => 2
		)
	)
);

Barlines are added to a Measure by setting to the "barline" property.

$measure->setProperty('barline', $barline);
$measure->setProperty('barline', array($barline1, $barline2));
$measure->addBarline($barline);

Part

A part is a sequence of measures intended to be played by the same instrument. Parts have a name, and an array of measures.

$part = new Part('Pianoforte', array());
$part->addMeasure($measure);

bv

Score

A score is the collection of all the parts being played by all the instruments. It is the highest-level element in MusicXML, and so it's usually where you will call the toXML() method.

$score = new Score();
$score->addPart($part);

echo $score->toXML();

Helper Classes

PHPMusicTools contains some interesting helper classes, that have no influence on typesetting scores, but which have application for music analysis or transformation.

Scale

The scale class is a utility that understands scales. A Scale object has three properties; scale, root, and direction.

The scale property is an integer between 0 and 4095, essentially a bitmask describing which tones are present in the scale.

You can create a scale using shorthand,

$scale = new Scale(2741, new Pitch('C', 1, 3), Scale::ASCENDING);

$scale = Scale::constructFromArray(array(
	'scale' => 2741,
	'root' => array(
		'step' => 'C',
		'alter' => 1,
		'octave' => 3,
	),
	'direction' => Scale::ASCENDING
));

Note that the root can be a heightless pitch to describe a heightless Scale, or it may be a pitch with an octave to anchor the scale at a certain height.

Scale objects are used for autoTune(), can be returned by functions that do analysis, and can be used to render sequences of Notes.

Visualizations

PHPMusicTools has a small (but growing!) collection of handy music visualization tools. Besides traditional notation, you can render things like guitar and ukelele diagrams, saxophone fingerings, piano keyboard diagrams, and theory diagrams like Scale Bracelets and Circle of Fifths diagrams.

All visualizations have a similar construction. Instantiate an instance of the class, and adjust its properties. Some of the properties will be required by the constructor, others will have defaults. The render() method returns the visualization as SVG markup. Each visualization has a static method that will render using just an array of properties.

Use the class constructor and render() method:

$v = new ScaleBracelet();
$v->scale = 2741;
$v->showImperfections = true;
$v->size = 300;
echo $v->render();

or, you use the static renderer:

echo \ianring\ScaleBracelet::renderFromArray(array(
	'scale' => 2741,
	'showImperfections' => true,
	'size' => 200,
	'symmetryLines' => array(1, 2.5, 4),
	),
	'curvedLines' => array(
		array('from' => 5, 'to' => 9, 'arrow' => 'start'),
	)
));

ScaleBracelet

Property Type What it does Sample
scale integer defines the Scale for this bracelet, fills those tones with black
size integer defines the size of the diagram. Actual width / height, in pixels.
showImperfections boolean If true, calculates the imperfections in this scale and puts an "i" on that tone.
showSymmetries boolean If true, calcuclates symmetries and displays them as a black line inside the circle.
symmetryLines array A more finely controllable way to show symmetry lines. Property is an array of numbers, from 0 to 11. Decimals like 3.5 are allowed
curvedLines array Each element of this array is an array with a "from" and "to" element, whose value is a scale position from 0 to 11. "arrow" can be "start", "end", or "both".
ridgeTones array Tones that should be marked as a Ridge Tone, with a large upper-case "R"
selected array Tones that should be filled in red.

PianoKeyboard

documentation coming soon

What should this library accomplish?

The big picture vision for this library is to be able to answer questions about music, generate representations of music programmatically, and manipulate music constructs. Here are a short list of goals:

  • Count many G's are in Moonlight Sonata
  • What is the pitch range of Chopin's Etudes?
  • Generate a D minor harmonic scale, starting at D3 and ascending for three octaves.
  • Generate a random 16th-note solo over the Giant Steps chord pattern, using no more than a major third leap
  • Take a score in Lilypond format in C, and output it transposed for Alto Saxophone.
  • Given a piece of music, guess what key and scale it's in
  • Create a trie that learns the next probable note given two notes and a body of music to analyze
  • Accept a MusicXML document and output it as MIDI
  • Identify French Sixth chords in a piece of music
  • Measure whether large leaps land on notes with longer durations.
  • Find places where a piano score requires a span of greater than an octave in a single hand
  • Check if a clarinet part has sufficient pauses for breath
  • Check if a flute part goes out of range
  • Find similar phrases in two or more pieces of music
  • Find parts that are out of range for their instrument
  • Locate tone rows in a score, and identify variants

Developer Info

If you're looking for something to work on, check the most wanted list at https://github.com/ianring/PHPMusicTools/issues Make sure all your code is good quality and that you've tested it, and run the Unit tests before submitting.

Unit Tests

All functionality should be unit tested. If you discover functionality that isn't tested yet, the highest priority should be to write tests for it. Though it is not mandated that all development should be test-driven, it is recommended that TDD is a good approach for adding new features or enhancing existing ones.

curl "https://phar.phpunit.de/phpunit-6.0.11.phar" -o "phpunit-6.0.phar"```
chmod +x phpunit-6.0.11.phar
sudo mv phpunit-6.0.11.phar /usr/local/bin/phpunit

This version of phpunit requires PHP 7.0 or above. If you're running an older version of PHP, you need to upgrade!

To run the tests,

cd ./ianring/PHPMusicTools/src/PHPMusicTools/test
phpunit

Test and Code Completion Report

To generate reports for test results and code completion (requires xdebug to be installed), run this command:

vendor/bin/phpunit --testdox-html public/reports/phpunit.html --whitelist src/PHPMusicTools/classes --coverage-html public/reports/coverage.html src/PHPMusicTools/test/

The reports will appear in the public/reports directory.

All in one build

For convenience, all the processing steps for documentation and code analysis are encapsulated in a build script:

./build.sh

Conventions

Object properties and methods are camel-case

$this->octaveChange = 1;

Documentation

PHPMusicTools uses phpdocumentor for api documentation, so all code must be properly self-documented. To generate the documentation in /public/docs/api, first you need to make sure you've already loaded the vendor extras using composer, then run this command:

vendor/bin/phpdoc -d ./src/PHPMusicTools/classes -t ./public/docs/api

If you are submitting a pull request into PHPMusicTools, do not alter any of the auto-generated files because they'll just be overwritten. Learn more at https://www.phpdoc.org/

You can host a mirror of the documentation by creating a symlink in a public folder

cd {your web root}/public_html/pmt/
ln -s ../../vendor/ianring/php-music-tools/public/docs/api/ docs

Code Standards

PHPMusicTools has a custom standard sniffer. To check the project for standards compliance, run this command, from the root:

vendor/bin/phpcs src/PHPMusicTools/classes/

Run the beautifier and fixer:

vendor/bin/phpcbf src/PHPMusicTools/classes/

License

GPL. See https://www.gnu.org/licenses/gpl-3.0.en.html .

I can't imagine what sort of trouble you could possibly get into using this code, but the authors and maintainers don't accept any responsibility for losses resulting from its use. It's a work in progress, so this comes with no warranteee whatsoever, and functionality may be incomplete or not exactly as described.

This code is not guaranteed to be safe, either. Use at your own risk.

Resources