mirror of
https://github.com/wader/fq.git
synced 2024-11-22 07:16:49 +03:00
midi: adding godoc (cf. https://github.com/transcriptaze/fq-midi/issues/2)
This commit is contained in:
parent
f150085735
commit
00ae470d48
@ -4,6 +4,7 @@ import (
|
||||
"github.com/wader/fq/pkg/scalar"
|
||||
)
|
||||
|
||||
// Map of note values to note names.
|
||||
var notes = scalar.UintMapSymStr{
|
||||
127: "G9",
|
||||
126: "F♯9/G♭9",
|
||||
@ -114,6 +115,7 @@ var notes = scalar.UintMapSymStr{
|
||||
21: "A0",
|
||||
}
|
||||
|
||||
// Map of key signature values to key signature names.
|
||||
const (
|
||||
keyCMajor = 0x0000
|
||||
keyGMajor = 0x0100
|
||||
|
13
format/midi/doc.go
Normal file
13
format/midi/doc.go
Normal file
@ -0,0 +1,13 @@
|
||||
/*
|
||||
Package midi implements an fq plugin to decode [standard MIDI files].
|
||||
|
||||
The MIDI decoder is a member of the 'probe' group and fq should automatically invoke the
|
||||
decoder when opening a MIDI file. The decoder can be explicitly specified with the '-d midi'
|
||||
command line option.
|
||||
|
||||
The decoder currently only supports MIDI 1.0 files and does only basic validation on the
|
||||
MIDI file structure.
|
||||
|
||||
[standard MIDI files]: https://midi.org/standard-midi-files.
|
||||
*/
|
||||
package midi
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/wader/fq/pkg/scalar"
|
||||
)
|
||||
|
||||
// MIDI meta-event status byte values.
|
||||
const (
|
||||
SequenceNumber uint64 = 0x00
|
||||
Text uint64 = 0x01
|
||||
@ -26,6 +27,7 @@ const (
|
||||
SequencerSpecificEvent uint64 = 0x7f
|
||||
)
|
||||
|
||||
// Maps MIDI meta-events to a human readable name.
|
||||
var metaevents = scalar.UintMapSymStr{
|
||||
SequenceNumber: "sequence_number",
|
||||
Text: "text",
|
||||
@ -47,6 +49,7 @@ var metaevents = scalar.UintMapSymStr{
|
||||
SequencerSpecificEvent: "sequencer_specific_event",
|
||||
}
|
||||
|
||||
// Internal map of MIDI meta-events to the associated event parser.
|
||||
var metafns = map[uint64]func(d *decode.D){
|
||||
SequenceNumber: decodeSequenceNumber,
|
||||
Text: decodeText,
|
||||
@ -68,6 +71,7 @@ var metafns = map[uint64]func(d *decode.D){
|
||||
SequencerSpecificEvent: decodeSequencerSpecificEvent,
|
||||
}
|
||||
|
||||
// decodeMetaEvent extracts the meta-event delta time, event status and event detail.
|
||||
func decodeMetaEvent(d *decode.D, event uint8, ctx *context) {
|
||||
ctx.running = 0x00
|
||||
ctx.casio = false
|
||||
@ -89,6 +93,8 @@ func decodeMetaEvent(d *decode.D, event uint8, ctx *context) {
|
||||
}
|
||||
}
|
||||
|
||||
// decodeSequenceNumber parses a Sequence Number MIDI meta event to a struct comprising:
|
||||
// - sequence_number
|
||||
func decodeSequenceNumber(d *decode.D) {
|
||||
d.FieldUintFn("length", vlq, d.UintRequire(2))
|
||||
d.FieldU16("sequence_number")
|
||||
|
@ -12,15 +12,18 @@ import (
|
||||
"github.com/wader/fq/pkg/interp"
|
||||
)
|
||||
|
||||
//go:embed midi.md
|
||||
var midiFS embed.FS
|
||||
|
||||
// context is a container struct for the running parse information required to
|
||||
// decode a MIDI track.
|
||||
type context struct {
|
||||
tick uint64
|
||||
running uint8
|
||||
casio bool
|
||||
}
|
||||
|
||||
//go:embed midi.md
|
||||
var midiFS embed.FS
|
||||
|
||||
// init registers the MIDI format decoder and adds it to the 'probe' group.
|
||||
func init() {
|
||||
interp.RegisterFormat(
|
||||
format.MIDI,
|
||||
@ -33,6 +36,10 @@ func init() {
|
||||
interp.RegisterFS(midiFS)
|
||||
}
|
||||
|
||||
// decodeMIDI implements the MIDI file decoder.
|
||||
//
|
||||
// The decoder parses the file as a set of chunks, each comprising a 4 character tag
|
||||
// followed by a uint32 length field. The decoder parses the MTHd and MTrk MIDI chunks.
|
||||
func decodeMIDI(d *decode.D) any {
|
||||
d.Endian = decode.BigEndian
|
||||
|
||||
@ -53,6 +60,12 @@ func decodeMIDI(d *decode.D) any {
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeMThd decodes an MThd MIDI header chunk into a struct with the fields:
|
||||
// - tag "MThd"
|
||||
// - length Header chunk size
|
||||
// - format MIDI format (0,1 or 2)
|
||||
// - tracks Number of tracks
|
||||
// - division Time division
|
||||
func decodeMThd(d *decode.D) {
|
||||
if !bytes.Equal(d.PeekBytes(4), []byte("MThd")) {
|
||||
d.Errorf("missing MThd tag")
|
||||
@ -76,6 +89,10 @@ func decodeMThd(d *decode.D) {
|
||||
})
|
||||
}
|
||||
|
||||
// decodeMTrk decodes an MTrk MIDI track chunk into a struct with the header fields:
|
||||
// - tag "MTrk"
|
||||
// - length Track chunk size
|
||||
// - events List of track events
|
||||
func decodeMTrk(d *decode.D) {
|
||||
if !bytes.Equal(d.PeekBytes(4), []byte("MTrk")) {
|
||||
d.Errorf("missing MTrk tag")
|
||||
@ -99,6 +116,10 @@ func decodeMTrk(d *decode.D) {
|
||||
})
|
||||
}
|
||||
|
||||
// decodeEvent decodes a single MIDI event as either:
|
||||
// - Meta event
|
||||
// - MIDI channel event
|
||||
// - SysEx system event
|
||||
func decodeEvent(d *decode.D, ctx *context) {
|
||||
_, status, event := peekEvent(d)
|
||||
|
||||
@ -113,6 +134,7 @@ func decodeEvent(d *decode.D, ctx *context) {
|
||||
}
|
||||
}
|
||||
|
||||
// peekEvent retrieves the type of the next event without moving the reader location.
|
||||
func peekEvent(d *decode.D) (uint64, uint8, uint8) {
|
||||
var N int = 1
|
||||
|
||||
@ -161,13 +183,14 @@ func peekEvent(d *decode.D) (uint64, uint8, uint8) {
|
||||
}
|
||||
}
|
||||
|
||||
// decodeOther decodes non-MIDI chunks as raw data.
|
||||
func decodeOther(d *decode.D) {
|
||||
d.FieldUTF8("tag", 4)
|
||||
length := d.FieldS32("length")
|
||||
d.FieldRawLen("data", length*8)
|
||||
}
|
||||
|
||||
// Big endian varint
|
||||
// vlq decodes a MIDI big-endian varuint.
|
||||
func vlq(d *decode.D) uint64 {
|
||||
vlq := uint64(0)
|
||||
|
||||
@ -185,7 +208,7 @@ func vlq(d *decode.D) uint64 {
|
||||
return vlq
|
||||
}
|
||||
|
||||
// Byte array with a big endian varint length
|
||||
// vlf decodes a MIDI byte array prefixed with a varuint length.
|
||||
func vlf(d *decode.D) ([]uint8, error) {
|
||||
N := vlq(d)
|
||||
|
||||
@ -200,7 +223,7 @@ func vlf(d *decode.D) ([]uint8, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// String with a big endian varint length
|
||||
// vlstring decodes a MIDI string prefixed with a varuint length.
|
||||
func vlstring(d *decode.D) string {
|
||||
if data, err := vlf(d); err != nil {
|
||||
d.Fatalf("%v", err)
|
||||
@ -211,6 +234,8 @@ func vlstring(d *decode.D) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// flush reads and discards any remaining bits in a chunk after encountering an
|
||||
// invalid event.
|
||||
func flush(d *decode.D, format string, args ...any) {
|
||||
d.Errorf(format, args...)
|
||||
|
||||
|
@ -5,6 +5,9 @@ import (
|
||||
"github.com/wader/fq/pkg/scalar"
|
||||
)
|
||||
|
||||
// MIDI event status byte values. A MIDI event status byte is a composite byte
|
||||
// composed of the event type in the high order nibble and the event channel
|
||||
// (0 to 15) in the low order nibble.
|
||||
const (
|
||||
NoteOff uint64 = 0x80
|
||||
NoteOn uint64 = 0x90
|
||||
|
3
format/midi/testdata/Makefile
vendored
3
format/midi/testdata/Makefile
vendored
@ -28,6 +28,9 @@ lint:
|
||||
fuzz: build
|
||||
make -f Makefile fuzz GROUP=midi
|
||||
|
||||
godoc:
|
||||
godoc -http=:80 -index_interval=60s
|
||||
|
||||
events: build
|
||||
go run . -d midi dv format/midi/testdata/events/sequence-number.mid
|
||||
go run . -d midi dv format/midi/testdata/events/text.mid
|
||||
|
Loading…
Reference in New Issue
Block a user