{"id":17245487,"url":"https://github.com/bobbicodes/nes-lisp-mml","last_synced_at":"2025-03-26T05:09:04.055Z","repository":{"id":227012480,"uuid":"770193584","full_name":"bobbicodes/nes-lisp-mml","owner":"bobbicodes","description":"Repo lives at: https://codeberg.org/bobbicodes/nes-lisp-mml","archived":false,"fork":false,"pushed_at":"2024-08-26T16:26:43.000Z","size":189155,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-31T06:42:43.270Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://bobbicodes.codeberg.page/nes-lisp-mml/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bobbicodes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-11T05:32:37.000Z","updated_at":"2024-10-22T12:18:20.000Z","dependencies_parsed_at":"2024-08-26T18:43:36.306Z","dependency_job_id":null,"html_url":"https://github.com/bobbicodes/nes-lisp-mml","commit_stats":null,"previous_names":["bobbicodes/nes-lisp-mml"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bobbicodes%2Fnes-lisp-mml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bobbicodes%2Fnes-lisp-mml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bobbicodes%2Fnes-lisp-mml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bobbicodes%2Fnes-lisp-mml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bobbicodes","download_url":"https://codeload.github.com/bobbicodes/nes-lisp-mml/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245591625,"owners_count":20640692,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T06:29:42.586Z","updated_at":"2025-03-26T05:09:04.035Z","avatar_url":"https://github.com/bobbicodes.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nes-lisp-mml\n\nThis is a tool for programmatically composing NES music. Songs are built using a dialect of the Lisp programming language, running in a live interpreter connected to a custom text editor. It is conceptually similar to [MML](https://en.wikipedia.org/wiki/Music_Macro_Language) (Music Macro Language), but benefits from Lisp's structured syntax which facilitates a highly ergonomic style of interactive evaluation. The interpreter is based on [MAL (Make-a-Lisp)](https://github.com/kanaka/mal) and closely follows Clojure including its [destructuring syntax](https://clojure.org/guides/destructuring), powerful [sequence processing library](https://clojure.org/reference/sequences) and [macro system](https://clojure.org/reference/macros). This project also aims to provide a more accessible composition environment for those with impaired vision or who otherwise have difficulty with graphical interfaces. \n\n## Evaluation key bindings\n\n- Shift+Enter = Eval top-level form\n- Alt/Cmd+Enter = Eval all\n- Ctrl+Enter = Eval at cursor\n\nThe *Eval at cursor* command is particularly powerful - it evaluates the expression that *ends* just to the left of the cursor position, allowing you to quickly test the result at each level of nesting.\n\n## API\n\nA part is represented by a sequence of commands, each of which is a hashmap with various keys representing `length` (expressed in 1/60 of a second ticks), `pitch` (MIDI numbers, including decimal values for vibrato/microtones), `volume` and `duty`. These sequences are passed to their respective channels.\n\nThe note data can be produced however you like, as long as it ends up a sequence of maps with the right keys. So the most basic way would be to use a literal sequence of maps:\n\n```clojure\n[{:volume 9 :length 20 :pitch 60} {:pitch 67} \n {:length 50 :pitch 65} {:length 20 :pitch 67}\n {:length 10 :pitch 68} {:pitch 67} {:pitch 65} \n {:pitch 67} {:length 20 :pitch 60}]\n```\n\nMuch of the time, it is enough to encode length/pitch pairs, which can be placed in the sequence using vectors. Thus the above example could be written like this:\n\n```clojure\n[{:volume 9 :length 20 :pitch 60} [20 67] [50 65] \n[20 67] [10 68] [10 67] [10 65] [10 67] [20 60]]\n```\n\nThere is no limit to the number of ways your music can be written. Check out the examples in the [songs folder](https://codeberg.org/bobbicodes/nes-lisp-mml/src/branch/main/src/songs) for inspiration.\n\n### Volume/duty cycle changes\n\nTo facilitate volume and duty changes, a note can also be given `volume` and `duty` keys. Volume is in 16 steps, from 0 to 15. Duty is from 0-3 (0-7 for VRC6):\n\n- 0 = 12.5%\n- 1 = 25%\n- 2 = 50%\n- 3 = 75%\n\nA volume or duty change is persistent, i.e. it will affect all subsequent notes until there is another change.\n\n### Noise pitches\n\nThe noise channel plays at 16 possible pitches from 0 (high) to 15 (low). Mode 1 noise (metallic sound) is from 16 to 32.\n\n### Volume envelopes\n\nTo create instruments using volume envelopes, you can define a sequence:\n\n```clojure\n(def saw-env\n  [30 27 23 19 15 11 8 7 7 7 7 7 6 6 6 6 6 6\n   5 5 5 5 5 5 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 1])\n```\n\nThen in the music sequence, select it with the `:envelope` key:\n\n```clojure\n  [{:envelope saw-env}\n   [12 33] [12 45] [12 33] [24 38] [12 36] [12 35] [12 36]\n   [12 33] [12 45] [12 33] [24 38] [12 36] [12 40] [12 28]]\n```\n\n### Looping\n\nMost songs contain many repeated patterns. To facilitate this without consuming additional data, 2 levels of loops are provided, `loop1` and `loop2`. You cannot nest a `loop1` or `loop2` inside another, but you can nest a `loop1` inside a `loop2` or vice versa. Just call `(loop1 \u003cn\u003e \u003cnotes\u003e)` where `n` is the number of times to loop, and `notes` is a sequence of notes. \n\n## Playing audio\n\nThe `play` function takes a map containing any combination of the following keys: `square1`, `square2`, `triangle`, `noise` and `dpcm`. For example:\n\n```clojure\n(play\n  {:square1 (concat [{:volume 4 :duty 0}] arps)\n   :square2 (concat [{:volume 1 :duty 0 :length 9 :pitch 160}]\n    (detune arps))\n   :triangle (concat tri2 tri3 tri2 tri4)\n   :noise (concat (loop1 3 drums1) drums2)})\n```\n\n## NSF/audio export\n\nTo save an audio file, pass a filename along with your note sequences to `save-wav`:\n\n```clojure\n(save-wav \"mytune.wav\"\n  {:square1 [[20 60] [20 67] [50 65] [20 67] [10 68]\n             [10 67] [10 65] [10 67] [20 60]]})\n```\n\nSaving an NSF file works the same way by calling `save-nsf`:\n\n```clojure\n(save-nsf \"mytune.nsf\"\n  {:square1 [[20 60] [20 67] [50 65] [20 67] [10 68]\n             [10 67] [10 65] [10 67] [20 60]]})\n```\n\nThese are just the basics - again, check out the example songs for lots of ideas.\n\n## Using DPCM samples\n\nThere is an *Import Sample* button on the bottom of the page that allows you to upload .dmc files. Once they are loaded, they can be referred to in the `:dpcm` key of the note maps passed to the above functions. The API is like this:\n\n```clojure\n(play\n  {:dpcm\n    [{:sample \"kick\" :length 3}\n     {:sample \"e-sus\" :length 12}\n     {:sample \"mute-e\" :length 11}\n     {:sample \"mute-e\" :length 11}\n     {:sample \"mute-e\" :length 11}\n     {:sample \"e-sus\" :length 16}\n     {:rest 32}]})\n```\n\nThe sample names must match the file names. Each note must have a `sample` key and a `length` key. Use `:rest` to insert gaps in the playback.\n\n## Panic!\n\nTo stop all audio currently playing, simply play a blank sequence:\n\n```clojure\n(play {})\n```\n\n## Building from source\n\nRequires [Node.js](https://nodejs.org/en/) version 14.18+, 16+.\n\n```\nnpm install\n```\n\n## Develop\n\n```\nnpm run dev\n```\n\n## Create optimized build\n\n```\nnpm run build\nnpm preview\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbobbicodes%2Fnes-lisp-mml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbobbicodes%2Fnes-lisp-mml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbobbicodes%2Fnes-lisp-mml/lists"}