forked from lichen-community-systems/youme
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lichen-community-systemsGH-5: Added initial docs for SMF file handlin…
…g and new events.
- Loading branch information
Showing
3 changed files
with
336 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
# "Standard" MIDI File Meta-Events | ||
|
||
[MIDI "Voice" messages](midi-events.md) are commonly passed between devices such as a keyboard and a synthesizer. There | ||
are also "meta" events that are unique to "Standard" MIDI files, which provide additional metadata about a track, | ||
and allow for things like changing the tempo while playing a series of "voice" messages. This page describes the | ||
supported range of messages and provides examples of each message type in the JSON format YouMe uses to represent them. | ||
|
||
## Sequence Number | ||
|
||
An optional event used to identify which "sequence" a track represents. Intended to occur at the beginning of a track. | ||
|
||
```json5 | ||
{ | ||
type: "sequenceNumber", | ||
value: 1 | ||
} | ||
``` | ||
|
||
## Text Event | ||
|
||
A free ASCII text event, generally notes or comments, and generally found at the beginning of a track. | ||
|
||
```json5 | ||
{ | ||
type: "text", | ||
value: "Best played on a busted speaker at the bottom of a well." | ||
} | ||
``` | ||
|
||
## Copyright Notice | ||
|
||
A copyright notice in ASCII text format. | ||
|
||
```json5 | ||
{ | ||
type: "copyright", | ||
value: "Copyright 2023, Tony Atkins" | ||
} | ||
``` | ||
|
||
## Track Name | ||
|
||
The ASCII name of the track. | ||
|
||
```json5 | ||
{ | ||
type: "name", | ||
value: "Percussion Section" | ||
} | ||
``` | ||
|
||
## Instrument Name | ||
|
||
An optional description of the type of instrument to be used to play a track. | ||
|
||
```json5 | ||
{ | ||
type: "instrumentName", | ||
value: "Bass Glockenspiel" | ||
} | ||
``` | ||
|
||
## Lyric | ||
|
||
An ASCII lyric representing one or more words or syllables to be sung at a point in time. Used heavily in Karaoke | ||
(.kar) files. | ||
|
||
```json5 | ||
{ | ||
type: "lyric", | ||
value: "a sea change" | ||
} | ||
``` | ||
|
||
## Marker | ||
|
||
An ASCII marker for a point in the sequence. | ||
|
||
```json5 | ||
{ | ||
type: "marker", | ||
value: "Chorus" | ||
} | ||
``` | ||
|
||
## Cue Point | ||
|
||
An ASCII "Cue Point", or kind of stage direction to assist in synchronising with film, video, or performance. | ||
|
||
```json5 | ||
{ | ||
type: "cuePoint", | ||
value: "All exit stage left." | ||
} | ||
``` | ||
|
||
## MIDI Channel Prefix | ||
|
||
A MIDI channel (0-15) to use for all voice messages contained in this track. Presumably supersedes the channel information | ||
used in individual voice messages for devices that support this. | ||
|
||
```json5 | ||
{ | ||
type: "channelPrefix", | ||
value: 3 | ||
} | ||
``` | ||
|
||
## End of Track | ||
|
||
Indicates that the track is complete. Required at the end of each track. | ||
|
||
```json5 | ||
{ | ||
type: "endOfTrack" | ||
} | ||
``` | ||
|
||
## Set Tempo | ||
|
||
The new tempo, in microseconds per quarter-note. Supports 3 bytes, or as many as `16,777,215` microseconds per quarter | ||
note. Again, this is _microseconds_, not milliseconds, so the maximum value is `16.777215` seconds. | ||
|
||
```json5 | ||
{ | ||
type: "tempo", | ||
value: 500000 // The default, 0.5 seconds per quarter note | ||
} | ||
``` | ||
|
||
## SMPTE Offset | ||
|
||
An optional representation of the starting time in [SMPTE format](https://en.wikipedia.org/wiki/SMPTE_timecode). Should | ||
be included at the beginning of the track. | ||
|
||
```json5 | ||
{ | ||
type: "smpteOffset", | ||
hour: 11, | ||
minute: 19, | ||
second: 25, | ||
frame: 5, | ||
fractionalFrame: 50 | ||
} | ||
``` | ||
|
||
The time values for `hour`, `minute` and `second` should be self-explanatory. The last two values assume a particular | ||
number of frames per second (set in the SMF file header), and indicate which `frame` and fraction of a frame | ||
(`fractionalFrame`) should be used as the starting point. | ||
|
||
## Time Signature | ||
|
||
The time signature from this point of the track forwards. TODO: Confirm that that is informational and doesn't actually | ||
affect the timing in most implementations. | ||
|
||
```json5 | ||
{ | ||
type: "timeSignature", | ||
numerator: 3, | ||
denominator: 4, | ||
midiClocksPerMetronomeClick: 24, | ||
thirtySecondNotesPerMidiQuarterNote: 4 | ||
} | ||
``` | ||
|
||
## Key Signature | ||
|
||
The key signature from this point of the track forwards. A positive `sf` value indicates the number of sharps. A | ||
negative `sf` value indicates the number of flats. An `sf` of zero indicates that there are no sharps or flats (i.e. the | ||
piece is in the key of C). The value of `mi` indicates whether the key is `major` or `minor`. | ||
|
||
```json5 | ||
{ | ||
type: "keySignature", | ||
sf: 4, | ||
mi: "major" | ||
} | ||
``` | ||
|
||
See [this page](https://musictheory.pugetsound.edu/mt21c/MajorKeySignatures.html) for more information on calculating | ||
the key from the number of sharps/flats, and vice versa. | ||
|
||
## Sequencer-Specific Meta-Event | ||
|
||
A specific set of instructions for a particular sequencer when reading a given MIDI file. The `value` here is the | ||
raw bytes as unsigned 8-bit integers. | ||
|
||
```json5 | ||
{ | ||
type: "sequencerSpecificMetaEvent", | ||
value: [ | ||
0x0B, | ||
0x13 | ||
] | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# "Standard" MIDI files | ||
|
||
A ["Standard" MIDI file](https://www.midi.org/specifications/file-format-specifications/standard-midi-files) is a way of | ||
representing a series of MIDI events along with information about when they should occur. YouMe provides support for | ||
reading and writing SMF files. | ||
|
||
A Standard MIDI file consists of a single "header" and on or more "tracks". A "track" is a series of MIDI events | ||
flagged with the relative time at which each event should occur. In addition to | ||
[the "voice" MIDI events](midi-events.md) supported by this package, "tracks" may include one or more | ||
["meta events"](smf-meta-events.md), such as key or tempo changes. | ||
|
||
This package provides functions to translate MIDI files into a JSON representation, and to produce MIDI files from the | ||
same JSON representation. To use this functionality in your work, you must include the core of YouMe and the file | ||
that enables SMF handling. See [the demos](../demos/index.html) and [tests](../tests/html/) for examples. | ||
|
||
## `youme.smf.parseSMFByteArray(byteArray)` | ||
|
||
* `byteArray {Uint8Array}`: An array of unsigned 8-bit integers representing the Standard MIDI file's contents. | ||
* Returns: An object representing the header and all tracks (see below). | ||
|
||
This function is designed to parse an array of unsigned 8-bit integers, such as you might obtain by uploading a file or | ||
performing [an XmlHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). | ||
|
||
### JSON Format | ||
|
||
The following is a representation of a single-track MIDI file that plays a single quarter note. | ||
|
||
```json5 | ||
{ | ||
errors: [], | ||
header: { | ||
format: 0, | ||
tracks: 1, | ||
division: { | ||
type: "ticksPerQuarterNote", | ||
resolution: 96 | ||
} | ||
}, | ||
tracks: [{ | ||
errors: [], | ||
events: [ | ||
{ | ||
timeElapsed: 96, | ||
messsage: { | ||
type: "noteOn", | ||
channel: 0, | ||
velocity: 127, | ||
note: 64 | ||
} | ||
}, | ||
{ | ||
timeElapsed: 96, | ||
messsage: { | ||
type: "noteOff", | ||
channel: 0, | ||
velocity: 0, | ||
note: 64 | ||
} | ||
}, | ||
{ | ||
timeElapsed: 0, | ||
metaEvent: { | ||
type: "endOfTrack" | ||
} | ||
} | ||
] | ||
}] | ||
} | ||
``` | ||
|
||
### Individual Events | ||
|
||
An individual event consists of a time at which it should occur (see below), and a MIDI message. There are two types of | ||
MIDI messages that can be use in track, "voice" messages and "meta events". | ||
|
||
"Voice" messages are things like "note on", "note off", and "pitch bend" messages, and are described in more detail in | ||
[MIDI Events](midi-events.md). "Meta events" are unique to SMF files, and are described in | ||
[the SMF meta-event docs](smf-meta-events.md). "Voice" messages are stored as a `message` element within an event. | ||
SMF "meta" events are stored as a `metaEvent` element within an event object. | ||
|
||
#### Measuring Time in MIDI Files | ||
|
||
Each MIDI event has two associated time values. The standard itself stores only a `deltaTime` value. This "delta time" | ||
represents the time elapsed between the previous event in the track and the current event. | ||
|
||
Our JSON structure also stores a `timeElapsed` value, which represents the total time elapsed since the start of the | ||
track. This information is intended to be helpful when searching and filtering for particular types of events, as you | ||
do not need to track the events you don't care about to calculate when the event should occur. | ||
|
||
These times are expressed in the time "division" specified in the header, and modified by various "meta" events, | ||
including "Set Tempo" and "SMPTE Offset" events (see [the SMF meta event docs](smf-meta-events.md)). You can think of | ||
the process as follows: | ||
|
||
1. Read the time signature. | ||
2. Calculate the amount of clock time represented by each unit of "delta" and "elapsed" time. | ||
3. The starting time is now unless there is a "SMPTE Offset" at the start of the track to indicate an initial delay. | ||
4. Move through events, incrementing the number of ticks based on their "delta time". | ||
5. If "Set Tempo" events are encountered, adjust the amount of clock time to allocate for each unit of "delta" and | ||
"elapsed" time. | ||
|
||
The initial "time per tick" as found in the header can either be expressed in terms of ticks per quarter note, or | ||
frames per second (SMPTE). Here is an example time signature that uses ticks per quarter note: | ||
|
||
```json5 | ||
{ | ||
header: { | ||
division: { | ||
type: "ticksPerQuarterNote", | ||
resolution: 96 | ||
} | ||
// Remaining header material | ||
} | ||
// Track information | ||
} | ||
``` | ||
|
||
The time per tick is determined by a "Set Tempo" meta event (see [the SMF meta event docs](smf-meta-events.md)). If no | ||
"Set Tempo" message is found, the default time per tick is 500,000 microseconds (120 BPM). In the above example, the | ||
`resolution` is 96, so there should be 5,208 microseconds (or 5.208 milliseconds) per tick. | ||
|
||
See [this page](https://www.recordingblogs.com/wiki/time-division-of-a-midi-file) for more examples, including more | ||
detail about SMPTE timing. | ||
|
||
### Error Handling | ||
|
||
Wherever possible, this function will attempt to parse as much data as possible. If errors occur, they are flagged in | ||
place, and also added to the next enclosing layer of the object, as follows: | ||
|
||
1. If there is an overall error processing the file that prevents reading track or header data, it will be recorded in | ||
the top-level `errors` array. | ||
2. If there is an error processing a track that is not associated with a particular event, the error will be recorded in | ||
the `errors` array for the track, and also included in the top-level `errors` array. | ||
3. If there is an error processing an event, it will be recorded in the event itself, in the `errors` array for the | ||
track, and in the top-level `errors` array. |