[{"element_type":"text","content":"\n  \n  \n        \u003cp\u003eWhen playing with synthesizer patches, it helps to have a MIDI sequencer to generate note triggers for you. One option is to use a DAW on a laptop, like Ableton or whatever. But, it can also be nice to go DAWless with a hardware sequencer, because buttons and knobs and blinking lights are fun. With DIY sequencer projects in mind, I wrote a plaintext music notation sequencer module for CircuitPython, called \u003ca href=\"https://github.com/samblenny/txtseq\" target=\"_blank\"\u003etxtseq\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThe sequencer can read a song from a text file on your CIRCUITPY drive, parse the music notation into an array of MIDI note on and off events, then play the MIDI events in an event loop (allowing time to run other code). The music notation is loosely based on a subset of the \u003ca href=\"https://abcnotation.com/wiki/abc:standard:v2.1\" target=\"_blank\"\u003eabc standard\u003c/a\u003e. Notation for note pitch, accidentals, octave, and duration is very similar to abc. For everything else, the sequencer uses a simpler grammar and syntax that is easy to parse on\u0026nbsp;a microcontroller.\u003c/p\u003e\n      \n\n","metadata":{}},{"element_type":"markdown","content":"\u003ch2\u003eCircuitPython + GarageBand Audio Demo\u003c/h2\u003e\n\n\u003cp\u003eIn my GitHub repository for the sequencer, the demonstration track I wrote is named \u003ccode class=\"inline\"\u003etrack1.txt\u003c/code\u003e. To hear what the commit 9c73ec4 version of\n\u003ca href=\"https://github.com/samblenny/txtseq/blob/9c73ec461f7b4e2ec9c85522e71e7e66b72405bd/track1.txt\"\u003e\u003ccode class=\"inline\"\u003etrack1.txt\u003c/code\u003e\u003c/a\u003e\nsounds like when played from a Trinket M0 over USB MIDI into a MIDI drum\ninstrument in GarageBand, you can listen to\n\u003ca href=\"https://github.com/samblenny/txtseq/blob/4ac122389c9a3fbff4fafa01339c08bfb4807cf2/demos/track1-180bpm.mp3\"\u003e\u003ccode class=\"inline\"\u003edemos/track1-180bpm.mp3\u003c/code\u003e\u003c/a\u003e.\u003c/p\u003e\n\n\u003ch2\u003eHow to Run the Code\u003c/h2\u003e\n\n\u003cp\u003eI've been testing this with CircuitPython 9.0.5 on a Trinket M0 (SAMD21), but\nmost of the code (all but MIDI out) also runs on desktop python3.\u003c/p\u003e\n\n\u003ch3\u003eCircuitPython Version\u003c/h3\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003ePrepare a host computer with something that can play sounds for incoming\nUSB MIDI notes on channels 10, 11, 12, and 13. For example, on macOS, you\ncan use the GarageBand app by adding a MIDI track to an empty project,\nthen selecting a drum kit as the MIDI track's instrument.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eUpdate CircuitPython and bootloader \u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython/installing-circuitpython\"\u003ethe normal way\u003c/a\u003e. (no additional libraries\nare needed)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eFetch a local copy of \u003ca href=\"https://github.com/samblenny/txtseq\"\u003etxtseq\u003c/a\u003e using \u003ca href=\"https://learn.adafruit.com/contribute-to-the-adafruit-learning-system-with-git-and-github/grab-your-fork-2#clone-your-repo-3103691\"\u003e\u003ccode class=\"inline\"\u003egit clone\u003c/code\u003e\u003c/a\u003e or by\ndownloading a release archive.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eCopy the \u003ccode class=\"inline\"\u003etxtseq/txtseq\u003c/code\u003e directory to your CIRCUITPY drive (see docs at \u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython/the-circuitpy-drive\"\u003eWelcome to CircuitPython! \u0026gt; The CIRCUITPY Drive\u003c/a\u003e)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eCopy \u003ccode class=\"inline\"\u003etxtseq/code.py\u003c/code\u003e, \u003ccode class=\"inline\"\u003etxtseq/boot.py\u003c/code\u003e, and \u003ccode class=\"inline\"\u003etxtseq/track1.txt\u003c/code\u003e to your\nCIRCUITPY drive\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp\u003eWhen \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e runs, it will parse music notation from \u003ccode class=\"inline\"\u003etrack1.txt\u003c/code\u003e into an\narray of MIDI note event data, then start playing the notes over USB MIDI. The\nparser and playback code print a variety of debug info to the \n\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython/kattni-connecting-to-the-serial-console\"\u003eserial console\u003c/a\u003e\nto help with measuring memory and CPU use along with MIDI playback latency.\u003c/p\u003e\n\n\u003ch3\u003eDesktop Version\u003c/h3\u003e\n\n\u003cp\u003eThis will give debug prints only, without actual MIDI playback. But, you could\neasily modify the code to use a library that is capable of sending MIDI. (see\ndefinition of \u003ccode class=\"inline\"\u003emidi_tx(data)\u003c/code\u003e callback function in \u003ccode class=\"inline\"\u003etxtseq/__main__.py\u003c/code\u003e)\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eClone the \u003ca href=\"https://github.com/samblenny/txtseq\"\u003etxtseq\u003c/a\u003e  repo\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e\u003ccode class=\"inline\"\u003ecd txtseq\u003c/code\u003e\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e\u003ccode class=\"inline\"\u003epython3 -m txtseq track1.txt\u003c/code\u003e\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch3\u003eExample Output\u003c/h3\u003e\n\n\u003cp\u003eThis is from running \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e on a Trinket M0 with CircuitPython 9.0.5:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e10: ppb=4\n11: bpm=180\n19: 1 ................\n20: 1 ...................\n21: 1 ...............................\n22: 1 ................\n23: 1 ...................\n24: 1 ......................\n[parse time: 517 ms]\n\n00009924 00078924 0010992A 0017892A 00189924 001F8924 0028992E 002F892E 00309928 00378928\n0040992A 0047892A 00489924 004F8924 0058992E 005F892E 00609924 00678924 0070992A 0077892A\n00789924 007F8924 0088992E 008F892E 00909928 00978928 00A0992A 00A7892A 00A89924 00AF8924\n00B8992E 00BF892E 00C09924 00C78924 00D0992A 00D7892A 00D89924 00DF8924 00E8992E 00EF892E\n00F09928 00F78928 0100992A 0107892A 01089924 010F8924 0118992E 011F892E 01209924 0120992E\n01278924 0127892E 0128992A 012F892A 0130992E 0137892E 01389924 013F8924 0140992E 0147892E\n0148992A 014F892A 01509928 01578928 0160992A 0167892A 01689924 016F8924 01789924 017F8924\n01809924 01838924 01849924 01878924 01889924 018B8924 018C9924 018F8924 01909924 01938924\n01949924 01978924 01989924 019B8924 019C9924 019F8924 01A09924 01A38924 01A49924 01A78924\n01A89924 01AB8924 01B09933 01B38933 01B49933 01B78933 01B89933 01BB8933 01BC9933 01BF8933\n01C09933 01C38933 01C49933 01C78933 01C89933 01CB8933 01CC9933 01CF8933 01D09933 01D38933\n01D49933 01D78933 01DC9924 01DF8924 01E09924 01E38924 01E49924 01E78924 01E89924 01EB8924\n01EC9924 01EF8924 01F09924 01F38924 01F49924 01F78924 01F89924 01FB8924 01FC9924 01FF8924\n02049931 020F8931 02109924 02178924 0220992A 0227892A 02289924 022F8924 0238992E 023F892E\n02409928 02478928 0250992A 0257892A 02589924 025F8924 0268992E 026F892E 02709924 02778924\n0280992A 0287892A 02889924 028F8924 0298992E 029F892E 02A09928 02A78928 02B0992A 02B7892A\n02B89924 02BF8924 02C8992E 02CF892E 02D09924 02D78924 02E0992A 02E7892A 02E89924 02EF8924\n02F8992E 02FF892E 03009928 03078928 0310992A 0317892A 03189924 031F8924 0328992E 032F892E\n03309924 0330992E 03378924 0337892E 0338992A 033F892A 0340992E 0347892E 03489924 034F8924\n0350992E 0357892E 0358992A 035F892A 03609928 03678928 0370992A 0377892A 03789924 037F8924\n03889924 038F8924 03909924 03938924 03949924 03978924 03989924 039B8924 039C9924 039F8924\n03A09924 03A38924 03A49924 03A78924 03A89924 03AB8924 03AC9924 03AF8924 03B09924 03B38924\n03B49924 03B78924 03B89924 03BB8924 03C09933 03C38933 03C49933 03C78933 03C89933 03CB8933\n03CC9933 03CF8933 03D09933 03D38933 03D49933 03D78933 03D89933 03DB8933 03DC9933 03DF8933\n03E09933 03E38933 03E49933 03E78933 03F09931 03FB8931\n[midi event dump time: 191 ms]\n\nmem_free: 10976 10880 9728   diffs: 96 1152\n\nPlaying on USB MIDI ch10-13...\nDone\n\u003c/pre\u003e\u003c/code\u003e\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eThe top section has debug print output from the parsing functions.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eThe second section is a dump of the array of timestamped midi events\ncreated by the note parsing code.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eThe third section summarizes \u003ccode class=\"inline\"\u003emem_free()\u003c/code\u003e measurements. (see \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eThe last section has debug prints from the MIDI event player.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2\u003eReading the Code\u003c/h2\u003e\n\n\u003cp\u003eThis section is for people who are interested in how the sequencer works. It's kind of technical, and I\nused a variety of optimization tricks to get it to fit and run well on a non-Express SAMD21 board.\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eThe txtseq module exports a function, \u003ccode class=\"inline\"\u003esequencer(f)\u003c/code\u003e (see \u003ccode class=\"inline\"\u003etxtseq/sequencer.py\u003c/code\u003e) which expects its\nargument, \u003ccode class=\"inline\"\u003ef\u003c/code\u003e, to be a binary mode file object (e.g. \u003ccode class=\"inline\"\u003eopen('some-song.txt', 'rb')\u003c/code\u003e). For usage\nexamples, see \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e or \u003ccode class=\"inline\"\u003etxtseq/__main__.py\u003c/code\u003e.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eThe \u003ccode class=\"inline\"\u003eseqencer()\u003c/code\u003e function parses its input file to find top level commands,\nwhich it then uses to call further parsing functions. For example, the\ncommands \u003ccode class=\"inline\"\u003e1\u003c/code\u003e, \u003ccode class=\"inline\"\u003e2\u003c/code\u003e, \u003ccode class=\"inline\"\u003e3\u003c/code\u003e, and \u003ccode class=\"inline\"\u003e4\u003c/code\u003e call the \u003ccode class=\"inline\"\u003ep_staff()\u003c/code\u003e function defined in \u003ccode class=\"inline\"\u003etxtseq/staff.py\u003c/code\u003e.\nThe numbers correspond to each of the four voices (tracks).\nNote on and off events generated by the parser get packed as uint32 values\nin an \u003ccode class=\"inline\"\u003earray.array('L')\u003c/code\u003e. The most significant 16 bits have a timestamp\n(units of MIDI pulses). The low 16 bits have MIDI status and data bytes.\nThis allows for adding note events one voice at a time without worrying\nabout out-of-order events. I sort the array at the end to merge all the\nevents from different voices into one list ordered by ascending timestamps.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eTo get all the parser code to compile and run on a SAMD21, the module is\nsplit into several files of less than 150 lines each. Also, I used several\nMicroPython optimization techniques from Damien George's 2018 PyCon AU talk,\n\"Writing fast and efficient MicroPython\".\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eThe \u003ccode class=\"inline\"\u003etxtseq/util.py\u003c/code\u003e file holds parsing functions for\ndealing with comments (\u003ccode class=\"inline\"\u003e# ...\u003c/code\u003e), semantically irrelevant whitespace, and\nsetting of header options (\u003ccode class=\"inline\"\u003eB\u003c/code\u003e for bpm, \u003ccode class=\"inline\"\u003eU\u003c/code\u003e for time unit)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eParsing of note pitch and duration for staff lines happens in the\n\u003ccode class=\"inline\"\u003ep_staff()\u003c/code\u003e function of \u003ccode class=\"inline\"\u003etxtseq/staff.py\u003c/code\u003e.\nThe parsing style is based on state machine loops that examine one byte at a\ntime, reading bytes with \u003ccode class=\"inline\"\u003ereadinto()\u003c/code\u003e to limit heap allocations. When one of\nthe parser functions or state machine branches recognizes a byte that should\nbe processed by a different function or state branch, it will rewind the\nfile's cursor position by one byte using the \u003ccode class=\"inline\"\u003ef.seek(mark)\u003c/code\u003e idiom.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003ePlayback uses a generator defined in \u003ccode class=\"inline\"\u003etxtseq/player.py\u003c/code\u003e.\nBy using the generator as the iterator for an event loop, it's easy to run\nyour own code interleaved with the MIDI player (see \u003ccode class=\"inline\"\u003emain()\u003c/code\u003e in \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e).\nAlso, holding playback state in the local scope of a generator makes it\npossible to avoid many heap allocations and dictionary lookups that would\nadd jitter and latency if the MIDI player used a class instance.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2\u003eMusic Notation Grammar and Syntax\u003c/h2\u003e\n\n\u003cp\u003eThis section is for people who want to write songs for the sequencer to play as MIDI events.\u003c/p\u003e\n\n\u003cp\u003eFor examples of how the plaintext music notation works, check out the comments\nin \u003ccode class=\"inline\"\u003etxtseq/track1.txt\u003c/code\u003e.\u003c/p\u003e\n\n\u003cp\u003eThe ASCII note transcription style used here is loosely based on the abc music\nstandard, but the two notations are not compatible. In particular, this\nnotation uses \u003ccode class=\"inline\"\u003e{}\u003c/code\u003e for chords, requires chord durations to be specified after\nthe closing \u003ccode class=\"inline\"\u003e}\u003c/code\u003e, and omits a lot of abc's features such as configurable key\nsignature.\u003c/p\u003e\n\n\u003cp\u003eThe short summary:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003eSingle note: \u003ccode class=\"inline\"\u003e\u0026lt;accidental\u0026gt;\u0026lt;pitch\u0026gt;\u0026lt;octave\u0026gt;\u0026lt;duration\u0026gt;\u003c/code\u003e (e.g. \u003ccode class=\"inline\"\u003eC\u003c/code\u003e \u003ccode class=\"inline\"\u003e_B,\u003c/code\u003e \u003ccode class=\"inline\"\u003ec2\u003c/code\u003e)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eRest: \u003ccode class=\"inline\"\u003ez\u0026lt;duration\u0026gt;\u003c/code\u003e (e.g. \u003ccode class=\"inline\"\u003ez\u003c/code\u003e \u003ccode class=\"inline\"\u003ez2\u003c/code\u003e \u003ccode class=\"inline\"\u003ez16\u003c/code\u003e)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eChord note: \u003ccode class=\"inline\"\u003e\u0026lt;accidental\u0026gt;\u0026lt;pitch\u0026gt;\u0026lt;octave\u0026gt;\u003c/code\u003e\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eChord: \u003ccode class=\"inline\"\u003e{\u0026lt;chord note\u0026gt;\u0026lt;chord note\u0026gt;...}\u0026lt;duration\u0026gt;\u003c/code\u003e (e.g. \u003ccode class=\"inline\"\u003e{C^DA}4\u003c/code\u003e \u003ccode class=\"inline\"\u003e{ceg}\u003c/code\u003e)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eAccidental: \u003ccode class=\"inline\"\u003e_\u003c/code\u003e (flat), \u003ccode class=\"inline\"\u003e^\u003c/code\u003e (sharp), or the empty string (natural)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eNote: \u003ccode class=\"inline\"\u003eC D E F G A B c d e f g a b\u003c/code\u003e (\u003ccode class=\"inline\"\u003eC\u003c/code\u003e is middle-c, \u003ccode class=\"inline\"\u003ec\u003c/code\u003e is 1 octave up)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eNote aliases: you can use some shorter aliases to write percussion parts:\n\u003ccode class=\"inline\"\u003eh\u003c/code\u003e for \u003ccode class=\"inline\"\u003e^F _G\u003c/code\u003e (closed hi-hat), \u003ccode class=\"inline\"\u003eH\u003c/code\u003e for \u003ccode class=\"inline\"\u003e_B ^A\u003c/code\u003e (open hi-hat), and \u003ccode class=\"inline\"\u003er\u003c/code\u003e for\n\u003ccode class=\"inline\"\u003e_e ^d\u003c/code\u003e (ride cymbal)\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eOctave: \u003ccode class=\"inline\"\u003e,\u003c/code\u003e (lower by 1 octave), \u003ccode class=\"inline\"\u003e'\u003c/code\u003e (raise by one octave), repeated commas\nor single-quotes are cumulative (\u003ccode class=\"inline\"\u003e,,\u003c/code\u003e lowers 2 octaves, \u003ccode class=\"inline\"\u003e'''\u003c/code\u003e raises by 3).\nExamples: \u003ccode class=\"inline\"\u003eC,,,\u003c/code\u003e  \u003ccode class=\"inline\"\u003ed'\u003c/code\u003e\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eDuration: An integer representing the length of a note or chord as a multiple\nof the current time unit. Duration is optional with a default value of 1.\nWith the time unit set for \u003ccode class=\"inline\"\u003e1/8\u003c/code\u003e, \u003ccode class=\"inline\"\u003eC2\u003c/code\u003e would mean a quarter note of middle-c,\nand \u003ccode class=\"inline\"\u003eC\u003c/code\u003e would be an eighth note.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eStaff: staff lines start with a voice number then have an arbitrary sequence\nof whitespace, bar lines (\u003ccode class=\"inline\"\u003e|\u003c/code\u003e), notes, and chords. Bar lines and whitespace\nare ignored by the parser, but you can use them to help organize your notes\nfor better readability. It's fine to make long notes that last for more than\none measure (e.g. \u003ccode class=\"inline\"\u003eC,16\u003c/code\u003e with time unit set to \u003ccode class=\"inline\"\u003e1/8\u003c/code\u003e would be played the same\nas 2 tied whole notes in 4/4 time)\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eExample: \u003ccode class=\"inline\"\u003e2 | {CDG}4 {ACD}4 | C2 C2 D2 G2 |\u003c/code\u003e\u003c/p\u003e\n\n\u003cp\u003eFor more examples, see \u003ccode class=\"inline\"\u003etxtseq/track1.txt\u003c/code\u003e\u003c/p\u003e\n\n\u003ch3\u003eSetting BPM and Time Unit\u003c/h3\u003e\n\n\u003cp\u003eThe \u003ccode class=\"inline\"\u003eB\u003c/code\u003e command sets bpm, which is a global setting that gets applied during playback.\nFor example, a line with \u003ccode class=\"inline\"\u003eB 120\u003c/code\u003e would set the playback speed to 120 beats per minute.\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode class=\"inline\"\u003eU\u003c/code\u003e command sets the time unit, which relates to the duration numbers. For example,\nYou can set the time unit to 1 eighth note with \u003ccode class=\"inline\"\u003eU 1/8\u003c/code\u003e command. In that case, if you wrote\n\u003ccode class=\"inline\"\u003eC2\u003c/code\u003e in a staff, that note's duration would be 2 eighth notes (1 quarter note). Or, for quarter\nnote triplets, you could use the \u003ccode class=\"inline\"\u003eU 1/4T\u003c/code\u003e command. In that case, the duration of a \u003ccode class=\"inline\"\u003eC2\u003c/code\u003e\nwould be 2/3 of one quarter note.\u003c/p\u003e\n\n\u003cp\u003eThe time unit options are: \u003ccode class=\"inline\"\u003e1/4\u003c/code\u003e, \u003ccode class=\"inline\"\u003e1/8\u003c/code\u003e, \u003ccode class=\"inline\"\u003e1/16\u003c/code\u003e, \u003ccode class=\"inline\"\u003e1/32\u003c/code\u003e, \u003ccode class=\"inline\"\u003e1/4T\u003c/code\u003e, \u003ccode class=\"inline\"\u003e1/8T\u003c/code\u003e, \u003ccode class=\"inline\"\u003e1/16T\u003c/code\u003e, and \u003ccode class=\"inline\"\u003e1/32T\u003c/code\u003e\u003c/p\u003e\n\n\u003ch3\u003eBass Clef Percussion Notes on Voice 1\u003c/h3\u003e\n\n\u003cp\u003eThe General MIDI standard includes a mapping of percussion sounds for MIDI\nchannel 10, notes 35 to 81. The sounds for a typical Western drum kit (kick,\nsnare, hi-hat, cymbal, etc) use notes in the bass clef, starting approximately\n2 octaves below middle C. To avoid having to write things like \u003ccode class=\"inline\"\u003eB,,,\u003c/code\u003e or \u003ccode class=\"inline\"\u003eC,,\u003c/code\u003e\nevery time you want a kick drum, voice 1 uses bass clef note names.\u003c/p\u003e\n\n\u003cp\u003eBass clef note names are 2 octaves (24 MIDI notes) lower than the equivalent\ntreble clef note names. So, bass clef \u003ccode class=\"inline\"\u003eC\u003c/code\u003e is the same MIDI note as \u003ccode class=\"inline\"\u003eC,,\u003c/code\u003e in\ntreble clef.\u003c/p\u003e\n\n\u003cp\u003eAlso, because the hi-hats and ride cymbal would otherwise need to be spelled\nwith a flat or sharp prefix, I included the aliases \u003ccode class=\"inline\"\u003eh\u003c/code\u003e, \u003ccode class=\"inline\"\u003eH\u003c/code\u003e, and \u003ccode class=\"inline\"\u003er\u003c/code\u003e. For\nquick reference, these are the notes for some common drum sounds:\u003c/p\u003e\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003cth\u003eBass Note\u003c/th\u003e\n\u003cth\u003eMIDI #\u003c/th\u003e\n\u003cth\u003eSound\u003c/th\u003e\n\u003c/tr\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eC\u003c/td\u003e\n\u003ctd\u003e36\u003c/td\u003e\n\u003ctd\u003eElectric Bass Drum\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eE\u003c/td\u003e\n\u003ctd\u003e40\u003c/td\u003e\n\u003ctd\u003eElectric Snare\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e^F _G h\u003c/td\u003e\n\u003ctd\u003e42\u003c/td\u003e\n\u003ctd\u003eClosed Hi-Hat\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e_B ^A H\u003c/td\u003e\n\u003ctd\u003e46\u003c/td\u003e\n\u003ctd\u003eOpen Hi-Hat\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e_e ^d r\u003c/td\u003e\n\u003ctd\u003e51\u003c/td\u003e\n\u003ctd\u003eRide Cymbal\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch3\u003ePriority Scheduling of MIDI Messages\u003c/h3\u003e\n\n\u003cp\u003eFor a MIDI link that can move 1 message per 1 ms, playing a chord of 3 notes\nwould take 3 ms to send. If the chord was meant to play on the same beat as a\ndrum strike and a CC update, the whole group would take 5 ms to send. But, if\nthe drum messages get sent first, the timing will sound tighter.\u003c/p\u003e\n\n\u003cp\u003eConsidering that low latency matters more for percussion, giving scheduling\npriority to messages for the percussion (usually MIDI channel 10) should help\nto make the most of available MIDI bandwidth.\u003c/p\u003e\n\n\u003cp\u003eTo allow for efficient percussion-priority sorting of MIDI events, I hardcoded\nthe player to use the following txtseq voice to MIDI channel mapping:\u003c/p\u003e\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003cth\u003evoice\u003c/th\u003e\n\u003cth\u003eMIDI channel\u003c/th\u003e\n\u003cth\u003eScheduling Priority\u003c/th\u003e\n\u003c/tr\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e1\u003c/td\u003e\n\u003ctd\u003e10\u003c/td\u003e\n\u003ctd\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e2\u003c/td\u003e\n\u003ctd\u003e11\u003c/td\u003e\n\u003ctd\u003e2\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e3\u003c/td\u003e\n\u003ctd\u003e12\u003c/td\u003e\n\u003ctd\u003e3\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e4\u003c/td\u003e\n\u003ctd\u003e13\u003c/td\u003e\n\u003ctd\u003e4\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n","metadata":{"markdown":"## CircuitPython + GarageBand Audio Demo\n\nIn my GitHub repository for the sequencer, the demonstration track I wrote is named `track1.txt`. To hear what the commit 9c73ec4 version of\n[`track1.txt`](https://github.com/samblenny/txtseq/blob/9c73ec461f7b4e2ec9c85522e71e7e66b72405bd/track1.txt)\nsounds like when played from a Trinket M0 over USB MIDI into a MIDI drum\ninstrument in GarageBand, you can listen to\n[`demos/track1-180bpm.mp3`](https://github.com/samblenny/txtseq/blob/4ac122389c9a3fbff4fafa01339c08bfb4807cf2/demos/track1-180bpm.mp3).\n\n## How to Run the Code\n\nI've been testing this with CircuitPython 9.0.5 on a Trinket M0 (SAMD21), but\nmost of the code (all but MIDI out) also runs on desktop python3.\n\n\n### CircuitPython Version\n\n1. Prepare a host computer with something that can play sounds for incoming\n   USB MIDI notes on channels 10, 11, 12, and 13. For example, on macOS, you\n   can use the GarageBand app by adding a MIDI track to an empty project,\n   then selecting a drum kit as the MIDI track's instrument.\n\n2. Update CircuitPython and bootloader [the normal way](https://learn.adafruit.com/welcome-to-circuitpython/installing-circuitpython). (no additional libraries\n   are needed)\n\n3. Fetch a local copy of [txtseq](https://github.com/samblenny/txtseq) using [`git clone`](https://learn.adafruit.com/contribute-to-the-adafruit-learning-system-with-git-and-github/grab-your-fork-2#clone-your-repo-3103691) or by\n   downloading a release archive.\n\n4. Copy the `txtseq/txtseq` directory to your CIRCUITPY drive (see docs at [Welcome to CircuitPython! \u0026amp;gt; The CIRCUITPY Drive](https://learn.adafruit.com/welcome-to-circuitpython/the-circuitpy-drive))\n\n5. Copy `txtseq/code.py`, `txtseq/boot.py`, and `txtseq/track1.txt` to your\n   CIRCUITPY drive\n\nWhen `code.py` runs, it will parse music notation from `track1.txt` into an\narray of MIDI note event data, then start playing the notes over USB MIDI. The\nparser and playback code print a variety of debug info to the \n[serial console](https://learn.adafruit.com/welcome-to-circuitpython/kattni-connecting-to-the-serial-console)\nto help with measuring memory and CPU use along with MIDI playback latency.\n\n\n### Desktop Version\n\nThis will give debug prints only, without actual MIDI playback. But, you could\neasily modify the code to use a library that is capable of sending MIDI. (see\ndefinition of `midi_tx(data)` callback function in `txtseq/__main__.py`)\n\n1. Clone the [txtseq](https://github.com/samblenny/txtseq)  repo\n\n2. `cd txtseq`\n\n3. `python3 -m txtseq track1.txt`\n\n\n### Example Output\n\nThis is from running `code.py` on a Trinket M0 with CircuitPython 9.0.5:\n\n```\n10: ppb=4\n11: bpm=180\n19: 1 ................\n20: 1 ...................\n21: 1 ...............................\n22: 1 ................\n23: 1 ...................\n24: 1 ......................\n[parse time: 517 ms]\n\n00009924 00078924 0010992A 0017892A 00189924 001F8924 0028992E 002F892E 00309928 00378928\n0040992A 0047892A 00489924 004F8924 0058992E 005F892E 00609924 00678924 0070992A 0077892A\n00789924 007F8924 0088992E 008F892E 00909928 00978928 00A0992A 00A7892A 00A89924 00AF8924\n00B8992E 00BF892E 00C09924 00C78924 00D0992A 00D7892A 00D89924 00DF8924 00E8992E 00EF892E\n00F09928 00F78928 0100992A 0107892A 01089924 010F8924 0118992E 011F892E 01209924 0120992E\n01278924 0127892E 0128992A 012F892A 0130992E 0137892E 01389924 013F8924 0140992E 0147892E\n0148992A 014F892A 01509928 01578928 0160992A 0167892A 01689924 016F8924 01789924 017F8924\n01809924 01838924 01849924 01878924 01889924 018B8924 018C9924 018F8924 01909924 01938924\n01949924 01978924 01989924 019B8924 019C9924 019F8924 01A09924 01A38924 01A49924 01A78924\n01A89924 01AB8924 01B09933 01B38933 01B49933 01B78933 01B89933 01BB8933 01BC9933 01BF8933\n01C09933 01C38933 01C49933 01C78933 01C89933 01CB8933 01CC9933 01CF8933 01D09933 01D38933\n01D49933 01D78933 01DC9924 01DF8924 01E09924 01E38924 01E49924 01E78924 01E89924 01EB8924\n01EC9924 01EF8924 01F09924 01F38924 01F49924 01F78924 01F89924 01FB8924 01FC9924 01FF8924\n02049931 020F8931 02109924 02178924 0220992A 0227892A 02289924 022F8924 0238992E 023F892E\n02409928 02478928 0250992A 0257892A 02589924 025F8924 0268992E 026F892E 02709924 02778924\n0280992A 0287892A 02889924 028F8924 0298992E 029F892E 02A09928 02A78928 02B0992A 02B7892A\n02B89924 02BF8924 02C8992E 02CF892E 02D09924 02D78924 02E0992A 02E7892A 02E89924 02EF8924\n02F8992E 02FF892E 03009928 03078928 0310992A 0317892A 03189924 031F8924 0328992E 032F892E\n03309924 0330992E 03378924 0337892E 0338992A 033F892A 0340992E 0347892E 03489924 034F8924\n0350992E 0357892E 0358992A 035F892A 03609928 03678928 0370992A 0377892A 03789924 037F8924\n03889924 038F8924 03909924 03938924 03949924 03978924 03989924 039B8924 039C9924 039F8924\n03A09924 03A38924 03A49924 03A78924 03A89924 03AB8924 03AC9924 03AF8924 03B09924 03B38924\n03B49924 03B78924 03B89924 03BB8924 03C09933 03C38933 03C49933 03C78933 03C89933 03CB8933\n03CC9933 03CF8933 03D09933 03D38933 03D49933 03D78933 03D89933 03DB8933 03DC9933 03DF8933\n03E09933 03E38933 03E49933 03E78933 03F09931 03FB8931\n[midi event dump time: 191 ms]\n\nmem_free: 10976 10880 9728   diffs: 96 1152\n\nPlaying on USB MIDI ch10-13...\nDone\n```\n\n1. The top section has debug print output from the parsing functions.\n\n2. The second section is a dump of the array of timestamped midi events\n   created by the note parsing code.\n\n3. The third section summarizes `mem_free()` measurements. (see `code.py`)\n\n4. The last section has debug prints from the MIDI event player.\n\n\n## Reading the Code\n\nThis section is for people who are interested in how the sequencer works. It's kind of technical, and I\nused a variety of optimization tricks to get it to fit and run well on a non-Express SAMD21 board.\n\n1. The txtseq module exports a function, `sequencer(f)` (see `txtseq/sequencer.py`) which expects its\n   argument, `f`, to be a binary mode file object (e.g. `open('some-song.txt', 'rb')`). For usage\n   examples, see `code.py` or `txtseq/__main__.py`.\n\n2. The `seqencer()` function parses its input file to find top level commands,\n   which it then uses to call further parsing functions. For example, the\n   commands `1`, `2`, `3`, and `4` call the `p_staff()` function defined in `txtseq/staff.py`.\n   The numbers correspond to each of the four voices (tracks).\n   Note on and off events generated by the parser get packed as uint32 values\n   in an `array.array('L')`. The most significant 16 bits have a timestamp\n   (units of MIDI pulses). The low 16 bits have MIDI status and data bytes.\n   This allows for adding note events one voice at a time without worrying\n   about out-of-order events. I sort the array at the end to merge all the\n   events from different voices into one list ordered by ascending timestamps.\n\n3. To get all the parser code to compile and run on a SAMD21, the module is\n   split into several files of less than 150 lines each. Also, I used several\n   MicroPython optimization techniques from Damien George's 2018 PyCon AU talk,\n   \"Writing fast and efficient MicroPython\".\n\n4. The `txtseq/util.py` file holds parsing functions for\n   dealing with comments (`# ...`), semantically irrelevant whitespace, and\n   setting of header options (`B` for bpm, `U` for time unit)\n\n5. Parsing of note pitch and duration for staff lines happens in the\n   `p_staff()` function of `txtseq/staff.py`.\n   The parsing style is based on state machine loops that examine one byte at a\n   time, reading bytes with `readinto()` to limit heap allocations. When one of\n   the parser functions or state machine branches recognizes a byte that should\n   be processed by a different function or state branch, it will rewind the\n   file's cursor position by one byte using the `f.seek(mark)` idiom.\n\n6. Playback uses a generator defined in `txtseq/player.py`.\n   By using the generator as the iterator for an event loop, it's easy to run\n   your own code interleaved with the MIDI player (see `main()` in `code.py`).\n   Also, holding playback state in the local scope of a generator makes it\n   possible to avoid many heap allocations and dictionary lookups that would\n   add jitter and latency if the MIDI player used a class instance.\n\n\n## Music Notation Grammar and Syntax\n\nThis section is for people who want to write songs for the sequencer to play as MIDI events.\n\nFor examples of how the plaintext music notation works, check out the comments\nin `txtseq/track1.txt`.\n\nThe ASCII note transcription style used here is loosely based on the abc music\nstandard, but the two notations are not compatible. In particular, this\nnotation uses `{}` for chords, requires chord durations to be specified after\nthe closing `}`, and omits a lot of abc's features such as configurable key\nsignature.\n\nThe short summary:\n\n- Single note: `\u0026lt;accidental\u0026gt;\u0026lt;pitch\u0026gt;\u0026lt;octave\u0026gt;\u0026lt;duration\u0026gt;` (e.g. `C` `_B,` `c2`)\n\n- Rest: `z\u0026lt;duration\u0026gt;` (e.g. `z` `z2` `z16`)\n\n- Chord note: `\u0026lt;accidental\u0026gt;\u0026lt;pitch\u0026gt;\u0026lt;octave\u0026gt;`\n\n- Chord: `{\u0026lt;chord note\u0026gt;\u0026lt;chord note\u0026gt;...}\u0026lt;duration\u0026gt;` (e.g. `{C^DA}4` `{ceg}`)\n\n- Accidental: `_` (flat), `^` (sharp), or the empty string (natural)\n\n- Note: `C D E F G A B c d e f g a b` (`C` is middle-c, `c` is 1 octave up)\n\n- Note aliases: you can use some shorter aliases to write percussion parts:\n  `h` for `^F _G` (closed hi-hat), `H` for `_B ^A` (open hi-hat), and `r` for\n  `_e ^d` (ride cymbal)\n\n- Octave: `,` (lower by 1 octave), `'` (raise by one octave), repeated commas\n  or single-quotes are cumulative (`,,` lowers 2 octaves, `'''` raises by 3).\n  Examples: `C,,,`  `d'`\n\n- Duration: An integer representing the length of a note or chord as a multiple\n  of the current time unit. Duration is optional with a default value of 1.\n  With the time unit set for `1/8`, `C2` would mean a quarter note of middle-c,\n  and `C` would be an eighth note.\n\n- Staff: staff lines start with a voice number then have an arbitrary sequence\n  of whitespace, bar lines (`|`), notes, and chords. Bar lines and whitespace\n  are ignored by the parser, but you can use them to help organize your notes\n  for better readability. It's fine to make long notes that last for more than\n  one measure (e.g. `C,16` with time unit set to `1/8` would be played the same\n  as 2 tied whole notes in 4/4 time)\n\n  Example: `2 | {CDG}4 {ACD}4 | C2 C2 D2 G2 |`\n\n  For more examples, see `txtseq/track1.txt`\n\n\n### Setting BPM and Time Unit\n\nThe `B` command sets bpm, which is a global setting that gets applied during playback.\nFor example, a line with `B 120` would set the playback speed to 120 beats per minute.\n\nThe `U` command sets the time unit, which relates to the duration numbers. For example,\nYou can set the time unit to 1 eighth note with `U 1/8` command. In that case, if you wrote\n`C2` in a staff, that note's duration would be 2 eighth notes (1 quarter note). Or, for quarter\nnote triplets, you could use the `U 1/4T` command. In that case, the duration of a `C2`\nwould be 2/3 of one quarter note.\n\nThe time unit options are: `1/4`, `1/8`, `1/16`, `1/32`, `1/4T`, `1/8T`, `1/16T`, and `1/32T`\n\n\n### Bass Clef Percussion Notes on Voice 1\n\nThe General MIDI standard includes a mapping of percussion sounds for MIDI\nchannel 10, notes 35 to 81. The sounds for a typical Western drum kit (kick,\nsnare, hi-hat, cymbal, etc) use notes in the bass clef, starting approximately\n2 octaves below middle C. To avoid having to write things like `B,,,` or `C,,`\nevery time you want a kick drum, voice 1 uses bass clef note names.\n\nBass clef note names are 2 octaves (24 MIDI notes) lower than the equivalent\ntreble clef note names. So, bass clef `C` is the same MIDI note as `C,,` in\ntreble clef.\n\nAlso, because the hi-hats and ride cymbal would otherwise need to be spelled\nwith a flat or sharp prefix, I included the aliases `h`, `H`, and `r`. For\nquick reference, these are the notes for some common drum sounds:\n\n| Bass Note | MIDI # | Sound               |\n| --------- | ------ | ------------------- |\n|  C        | 36     | Electric Bass Drum  |\n|  E        | 40     | Electric Snare      |\n| \\^F \\_G h   | 42     | Closed Hi-Hat       |\n| \\_B \\^A H   | 46     | Open Hi-Hat         |\n| \\_e \\^d r   | 51     | Ride Cymbal         |\n\n\n### Priority Scheduling of MIDI Messages\n\nFor a MIDI link that can move 1 message per 1 ms, playing a chord of 3 notes\nwould take 3 ms to send. If the chord was meant to play on the same beat as a\ndrum strike and a CC update, the whole group would take 5 ms to send. But, if\nthe drum messages get sent first, the timing will sound tighter.\n\nConsidering that low latency matters more for percussion, giving scheduling\npriority to messages for the percussion (usually MIDI channel 10) should help\nto make the most of available MIDI bandwidth.\n\nTo allow for efficient percussion-priority sorting of MIDI events, I hardcoded\nthe player to use the following txtseq voice to MIDI channel mapping:\n\n| voice | MIDI channel | Scheduling Priority |\n| ----- | ------------ | ------------------- |\n| 1     | 10           | 1                   |\n| 2     | 11           | 2                   |\n| 3     | 12           | 3                   |\n| 4     | 13           | 4                   |\n"}},{"element_type":"markdown","content":"\u003ch2\u003eHardware\u003c/h2\u003e\n\n\u003cp\u003eI developed the sequencer on a Trinket M0 (non-Express SAMD21) to make sure it would be small and fast. While I haven't tested on other CircuitPython boards, probably the code will run fine, as long as you use a board that supports USB MIDI. If you want to use hardwired DIN-5 or TRS MIDI, take a look at the \u003ccode class=\"inline\"\u003emidi_tx()\u003c/code\u003e callback\nfunction defined in \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e.\u003c/p\u003e\n","metadata":{"markdown":"## Hardware\n\nI developed the sequencer on a Trinket M0 (non-Express SAMD21) to make sure it would be small and fast. While I haven't tested on other CircuitPython boards, probably the code will run fine, as long as you use a board that supports USB MIDI. If you want to use hardwired DIN-5 or TRS MIDI, take a look at the `midi_tx()` callback\nfunction defined in `code.py`."}},{"element_type":"product","content":"https://www.adafruit.com/product/3500","metadata":{}}]