https://github.com/bobbicodes/nes-lisp-mml
Repo lives at: https://codeberg.org/bobbicodes/nes-lisp-mml
https://github.com/bobbicodes/nes-lisp-mml
Last synced: about 1 year ago
JSON representation
Repo lives at: https://codeberg.org/bobbicodes/nes-lisp-mml
- Host: GitHub
- URL: https://github.com/bobbicodes/nes-lisp-mml
- Owner: bobbicodes
- Created: 2024-03-11T05:32:37.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2024-08-26T16:26:43.000Z (almost 2 years ago)
- Last Synced: 2025-01-31T06:42:43.270Z (over 1 year ago)
- Language: JavaScript
- Homepage: https://bobbicodes.codeberg.page/nes-lisp-mml/
- Size: 180 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# nes-lisp-mml
This 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.
## Evaluation key bindings
- Shift+Enter = Eval top-level form
- Alt/Cmd+Enter = Eval all
- Ctrl+Enter = Eval at cursor
The *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.
## API
A 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.
The 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:
```clojure
[{:volume 9 :length 20 :pitch 60} {:pitch 67}
{:length 50 :pitch 65} {:length 20 :pitch 67}
{:length 10 :pitch 68} {:pitch 67} {:pitch 65}
{:pitch 67} {:length 20 :pitch 60}]
```
Much 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:
```clojure
[{:volume 9 :length 20 :pitch 60} [20 67] [50 65]
[20 67] [10 68] [10 67] [10 65] [10 67] [20 60]]
```
There 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.
### Volume/duty cycle changes
To 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):
- 0 = 12.5%
- 1 = 25%
- 2 = 50%
- 3 = 75%
A volume or duty change is persistent, i.e. it will affect all subsequent notes until there is another change.
### Noise pitches
The noise channel plays at 16 possible pitches from 0 (high) to 15 (low). Mode 1 noise (metallic sound) is from 16 to 32.
### Volume envelopes
To create instruments using volume envelopes, you can define a sequence:
```clojure
(def saw-env
[30 27 23 19 15 11 8 7 7 7 7 7 6 6 6 6 6 6
5 5 5 5 5 5 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 1])
```
Then in the music sequence, select it with the `:envelope` key:
```clojure
[{:envelope saw-env}
[12 33] [12 45] [12 33] [24 38] [12 36] [12 35] [12 36]
[12 33] [12 45] [12 33] [24 38] [12 36] [12 40] [12 28]]
```
### Looping
Most 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 )` where `n` is the number of times to loop, and `notes` is a sequence of notes.
## Playing audio
The `play` function takes a map containing any combination of the following keys: `square1`, `square2`, `triangle`, `noise` and `dpcm`. For example:
```clojure
(play
{:square1 (concat [{:volume 4 :duty 0}] arps)
:square2 (concat [{:volume 1 :duty 0 :length 9 :pitch 160}]
(detune arps))
:triangle (concat tri2 tri3 tri2 tri4)
:noise (concat (loop1 3 drums1) drums2)})
```
## NSF/audio export
To save an audio file, pass a filename along with your note sequences to `save-wav`:
```clojure
(save-wav "mytune.wav"
{:square1 [[20 60] [20 67] [50 65] [20 67] [10 68]
[10 67] [10 65] [10 67] [20 60]]})
```
Saving an NSF file works the same way by calling `save-nsf`:
```clojure
(save-nsf "mytune.nsf"
{:square1 [[20 60] [20 67] [50 65] [20 67] [10 68]
[10 67] [10 65] [10 67] [20 60]]})
```
These are just the basics - again, check out the example songs for lots of ideas.
## Using DPCM samples
There 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:
```clojure
(play
{:dpcm
[{:sample "kick" :length 3}
{:sample "e-sus" :length 12}
{:sample "mute-e" :length 11}
{:sample "mute-e" :length 11}
{:sample "mute-e" :length 11}
{:sample "e-sus" :length 16}
{:rest 32}]})
```
The 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.
## Panic!
To stop all audio currently playing, simply play a blank sequence:
```clojure
(play {})
```
## Building from source
Requires [Node.js](https://nodejs.org/en/) version 14.18+, 16+.
```
npm install
```
## Develop
```
npm run dev
```
## Create optimized build
```
npm run build
npm preview
```