- Crystal 96.1%
- Makefile 2.6%
- Shell 1%
- Dockerfile 0.3%
| doc | ||
| dockerfiles | ||
| scripts | ||
| spec | ||
| src | ||
| vendor | ||
| vscode/ramekin | ||
| .editorconfig | ||
| .gitignore | ||
| .gitmodules | ||
| compile-cross.sh | ||
| cross.mk | ||
| docker-compose.yml | ||
| LICENSE | ||
| logo.svg | ||
| Makefile | ||
| README.md | ||
| release.sh | ||
| shard.lock | ||
| shard.yml | ||
Ramekin
Ramekin is a pre-processor for AddMusicK syntax, and an all-purpose tool for composing soundtracks to SMW romhacks, ready to use by hack authors.
Since Ramekin 1.0.0, I make a reasonable commitment to backwards-compatibility. Releases are tested against a collection of real-world ports. If I accidentally break something on a release, please let me know in the Issues forum here, on smwcentral, or any other place you may find me online.
For a preview of what full Ramekin tracks look like, feel free to check out the examples repo.
If you'd like to read about Ramekin syntax without installing or setting anything up, scroll down to the syntax guide.
Installation
On Windows
Download and run ramekin-installer.exe from the latest entry on the Releases page.
On MacOS
You can install Ramekin from my homebrew tap:
$ brew tap jneen/tap https://codeberg.org/jneen/tap
$ brew install ramekin
Elsewhere
Grab ramekin-X.Y.Z.zip from the Releases page, extract it, and put whichever is the appropriate binary for your system somewhere on your PATH. I like to use ~/.local/bin.
Then run ramekin's setup command:
$ ramekin setup
See Building below for making custom builds or compiling ramekin yourself.
Usage
ramekin is a command line (CLI) program - that means you need to run it in a terminal, like powershell or git bash on windows, gnome-terminal, kitty, iTerm, or Terminal.app on mac. If you set it up correctly, you should be able to just type in ramekin and press enter, and it will print the following usage instructions:
usage: ramekin [--pack NAME=PATH] [command] [flags]
(default command: compile)
ramekin --pack my_cool_pack=/path/to/my_cool_pack [...]
Specifies an additional custom sample location.
Files compiled this way should use #pack "my_cool_pack",
and sample paths relative to the deepest common path that
contains .brr files. Use with `package --list my_cool_pack` to
see details.
ramekin filename.rmk [flags]
flags:
-h --help
display this message
-i filename.rmk
set the input file
--txt filename.txt
output amk txt to this file.
use a single dash (-) to print txt to stdout.
--spc filename.spc
use AddmusicK to output an SPC.
--play
play the generated SPC file.
--play N
--play =N
--play @L:C
--play .my-cool-mark
play starting from an offset.
- if N is a number, start from N seconds into the file.
- if =N is given, start from N ticks into the file.
- if @L:C is given, start from the nearest note before
the given line and column.
- if .my-cool-mark is given, find where #.my-cool-mark
appears in the source and play from there.
- ticks, line, and mark offsets are approximate.
--install path/to/amk_dir
install the track to an existing AddmusicK directory.
copies over the txt and all samples, but you must add
it to Addmusic_list.txt manually.
--package my-cool-folder/
create a package directory suitable for submission
to SMWC, complete with packaged samples.
--wav filename.wav N
render N seconds of the file to WAV
--stems dirname N
render N seconds of separated WAV files, one per channel
--vis filename.png
outputs AddmusicK's ARAM visualization to a specified file
--mute 123
-m 123
mutes the selected channels when playing or rendering.
ramekin pack [flags]
flags:
--update
download BRR packs from SMW Central.
--search [text]
search for BRR filenames (e.g. "marimba")
--list [text]
search for pack names (e.g. "chrono trigger")
and list their samples
--list-smw
list the builtin instruments from SMW that can be used
without a pack.
ramekin setup [flags]
flags:
--amk
downloads and sets up AddMusicK and asar.
(requires cmake and make to be available)
--packs
downloads packages from SMW Central.
(equivalent to pack --update)
--all (default)
runs all of the above setup steps
--force
deletes and redownloads files instead of skipping.
It's ok if you don't get all of that at once, just come back to that as a reference for how to run the program. For now, try typing:
ramekin setup
Ramekin will now start downloading and indexing everything it needs to compile and play music.
Instead of making you put your music files in a particular special location, ramekin allows you to put them wherever you want. Make a music file with an .rmk extension (or grab an example track and type:
ramekin my_cool_file.rmk
The usage instructions above show a number of other ways to output ramekin files, including --spc out.spc for an SPC file, --txt out.txt for an AddmusicK-compatible txt file, --package my_package_dir for a directory you can zip up and submit to SMWCentral (but edit the README first), and even --vis my_png_file.png for AddmusicK's aram visualization png (can be useful if you're running out of aram!).
Configuration
The first time ramekin runs, it will create a configuration file in ~/.ramekin/config.yml (where ~ is your home directory). To customize where Ramekin keeps things, edit this file with your favourite text editor, and be sure to remove the # from the start of lines you want to take effect. If you want Ramekin to keep its files in some other place, override the RAMEKIN_HOME environment variable.
If you already know AddmusicK
See the Quick overview for experienced AddmusicK users.
SMW Music Concepts
Before we get in to writing your first Ramekin piece, there are a couple of general concepts about the SNES chip and the SMW/AddmusicK sound driver to be aware of.
Samples
Almost all sounds played on the SNES sound chip are from samples - small loopable audio snippets in BRR format. Several samples are included in SMW already and must be loaded for sound effects to work properly.
You can additionally load samples from SMW Central or custom BRR files made with tools like BRRTools.
Channels
SNES audio is organized into 8 channels, numbered 0-7. But in the context of a romhack, channels 6 and 7 are reserved for sound effects.
This is the biggest limitation of romhack music: You must generally arrange your music to fit in 6 voices, including drums. Each channel can only play one note at a time - for chords, you must use multiple channels. However, channels can switch instruments extremely quickly - so things can be interleaved if you are clever with instrument switches. For example, many songs can use a single channel for drums by quickly switching between kicks, snares, and hi-hats.
ARAM
ARAM, or "Audio RAM", is the memory available on the sound chip. This needs to hold many things at the same time, including:
- SMW samples for sound effects
- Any custom samples
- An echo buffer - where audio data yet to be echoed is stored
- Note data for your song
- Note data for sound effects and global songs (p-switch, victory, etc)
ARAM is the second biggest limitation of romhack music, and if you use too much you will likely see the dreaded "Echo buffer exceeded total space in ARAM" error (this will sometimes happen even if the problem isn't echo - echo is just the last thing to be reserved).
The main ways to deal with running out of ARAM are:
- Add
#optimizedto your preamble to use lower-quality SMW samples - Reduce the number of custom samples used, and try some SMW instruments instead
- Reduce echo delay
Syntax
In Ramekin, all info about a song (except non-SMWC custom samples) is included in a single file ending in .rmk. The syntax of this file is very similar to AddmusicK's .txt syntax. Comments begin with ; and span to the end of the line, and most commands are either single characters, or directives marked with #.
Metadata / preamble
The top of a Ramekin file, before the first channel is declared, is the preamble. Here, various commands can be used, starting with #.
Metadata commands
These commands set important metadata headers for the SPC.
#title "My cool songle"
#game "title of the game, if any"
#author "my name"
#comment "usually something about who ported the track"
Additionally, you can specify additional #readme text to be included in the packaged song:
#readme "
Here is some very important information about running this track.
"
The directory name that ramekin uses for samples can be set here as well. Note that this doesn't have to correspond to any actual directory - ramekin will automatically organize your samples in a directory with this name when exporting. Make sure to use a unique enough name here if submitting to SMW Central. If not provided, ramekin will use the filename without .rmk as the sample path.
; sets the sample dir name to my-cool-port/
#path "my-cool-port"
Finally, some general options can be set:
; causes the track not to speed up when the timer is low
#option TempoImmunity
; sets the global volume, 0-255.
; Try to aim for -21 to -19 lufs to balance well with sound effects.
w150
; sets the bpm. (todo: mention the reasonable range for this)
#bpm:130
Instrument definitions
Default Instruments
Ramekin can use the #default and #optimized native sample groups for SMW. Just write:
#default
; or
#optimized
The #default sample group is implied if nothing else is specified, but you can use #optimized if you are running low on ARAM.
Using native SMW instruments is the easiest way to get started quickly. To see the full list of available SMW instruments, use:
$ ramekin pack --list-smw
;;; List of builtin instruments from SMW: ;;;
@smwflute ; @0
@smwstring ; @1
@smwglock ; @2
@smwmarimba ; @3
@smwcello ; @4
@smwsteelguitar ; @5
@smwtrumpet ; @6
@smwsteeldrum ; @7
@smwacousticbass ; @8
@smwpiano ; @9
@smwsnare ; @10
@smwstring2 ; @11
@smwbongo ; @12
@smwep ; @13
@smwslapbass ; @14
@smworchhit ; @15
@smwharp ; @16
@smwdistguitar ; @17
@smwkick ; @21
@smwhat ; @22
@smwshaker ; @23
@smwwoodblock ; @24
@smwhiwoodblock ; @25
@smwdrums ; @28
@smwpower ; @29
Sample Packs
To get more diverse sounds on the SNES, it is often nice to use samples, either ripped from other games or converted to BRR (the SNES's native sample format).
Ramekin allows you to extremely easily use BRR packs from SMW Central's repositories. These not only contain BRR samples, but also the tuning and ADSR data to make them sound great by default.
To load a pack from SMW Central, use #pack with the pack's name on SMW Central. For example, let's say I wanted to use samples from Chrono Trigger. I can search that with:
ramekin pack --list chrono
This will show us a pack named "Chrono Trigger" along with a bunch of files with a .brr extension. To use the pack, add:
#pack "Chrono Trigger"
Then, to load, say, the sitar from the pack, we use:
#instrument @sitar "Sitar.brr"
This defines a new instrument called @sitar to point to Sitar.brr with the default settings. You can also leave off the ".brr" extension if you like. Later, you can use the command @sitar to switch to this instrument.
After the instrument declaration, you can use several commands to change the default settings, such as #adsr, #tuning and o:
#instrument @sitar #adsr:0,4,2,23 #tuning:1a2f o5
#instrument @sitar2 #gain/decrease:20
It is generally advised to stick with the default tuning, which is automatically read from the pack. The o5 declaration sets the default octave for the instrument to octave 5 (the default is 4). Whenever this instrument is selected, Ramekin will insert a switch to the specified octave.
Echo settings
Echo settings can be set up with the #echo/... family of directives:
; select which channels have echo enabled
#echo/channels:0,1,2,3
; or
#echo/channels:all
; or
#echo/channels:none
#echo/delay:3 ; the delay length of the echo.
#echo/volume:20 ; range from -80 to 7f, use negatives for surround
#echo/volume:20,30 ; set different values for left vs right echo
#echo/feedback:48 ; range from -80 to 7f, use negatives for surround
#echo/fir:1 ; 1 or 0 to enable/disable the FIR filter
; later, you can use this to toggle the echo on a channel.
#echo/toggle
Be particularly careful with #echo/delay:N - high numbers will use quite a lot of ARAM, and reduce the number and quality of custom samples you can use. Typically 2 or 3 is fine, but if I have a lot of ARAM available and want a very soupy sound I might consider 4 or 5.
Channel Commands
With your metadata all set up, it's time to add some notes!
Notes
To start adding notes to a channel, use #N, where N is the channel number:
#0
; now we're in channel 0!
Next, switch to an instrument, and add some notes:
#0
@smwacousticbass
o3
c8d8e8f8
this plays 8th notes c, d, e, and f in octave 3 on the instrument @smwacousticbass.
For sharps and flats, use + and -, and for rests, use the special note r:
#0 @smwacousticbass o5
d8 e-8 f+8 g8 r2
This plays d, e-flat, f-sharp, g in 8th notes, followed by a half-note rest. To hear it, save this to a file named my-cool-song.rmk, and run
ramekin my-cool-song.rmk --play
Press q when you are done. Notice that it loops to the beginning by default! The default behaviour of the AddMusicK/smw music system is to loop as soon as the first defined channel runs out of notes.
To set the loop point somewhere that is not the beginning, use the / command:
; will return to the / when it hits the loop point
#0 @smwacousticbass o5
d8 e-8 / f+8 g8 r2
For more complex note lengths, dots and ties are available. For example, b8. plays a b for a dotted eigth note length. This is equivalent to b8^16, which plays for an 8th note tied to a 16th note. Any number of ties can be used in sequence to create any length of note. Ramekin will simplify note lengths and combine rests automatically.
Because it is common to play a bunch of fast notes all the same length, the l command allows setting a default length to use when no note length is specified. So our example from before is equivalent to:
#0 @smwacousticbass o5
l8 de-f+g r2
Personally, I tend to use l16 across the whole track for consistency.
Loops and loop calls
Just as in Addmusick, there are two kinds of loops:
- Regular loops, marked with
[ ], which can optionally be tagged with a name, as in(my-cool-loop)[ ]. These can not only loop immediately but also be called at arbitrary points. - Super loops, marked with
[[ ]], which can only loop immediately, but can nest either inside or outside of regular loops.
This is best shown with an example:
#0 @smwacousticbass l16
[ defg ]4 ; plays defg four times (regular loop)
[[ abcd ]]4 ; plays abcd four times (superloop)
* ; calls the last defined regular loop, in this case plays defg
*3 ; same thing but 3 times
(blungus)[ fedc ] ; defines a loop named (blungus), also plays fedc immediately
(blungus) ; calls the loop (blungus)
(blungus)2 ; calls the loop (blungus) twice
Expression and effects
Volume Expression
Volume can be set with vXXX, where XXX is a number between 0 and 255. This command also sets the current "reference volume", which can be referenced with commands v+XX and v-XX, where XX is a number to be added or subtracted to the reference volume. This way, you can add dynamics to your drums or melody lines, while also easily being able to change the overall volume of the section.
; sets the volume and reference volume to 100
v100
; sets the volume to 150, referencing 100 as the reference volume
v+50
; sets the volume to 50, referencing 100 as the reference volume
v-50
Generally speaking, I recommend setting a reference volume at the start of a section, and using v+X and v-X, which won't modify the reference volume, to add dynamics within the phrase.
Note that all of these are rendered into absolute volume numbers at compile time, so the reference volume is based on where a not physically appears in the file, even if it's called later by a loop label.
Additionally, you may set the velocity of a note using a value u0 to u15. Unlike velocity, these commands are "free" - they are an intrinsic part of a note, rather than a channel setting that is changed during play. uXX is 15 by default, and gets reset any time an instrument is switched. See "Understanding intrinsic note properties" below.
Since uXX also supports relative syntax similar to v+X and v-X, I often have macros such as "F=u-0", "M=u-4", and "P=u-8" for forte, mezzo, and piano, which I will use mid-phrase to define dynamics.
If you're familiar with DAW workflows, you can think of vXX like automating the volume slider on a channel, whereas uXX modifies individual note velocities.
Pitch Expression
Pitch expression can be accomplished in two ways:
pA,B,C, where A,B, and C are numbers 0-255, can be used to set vibrato - a slight pitch wiggle that starts sometime after a note is started.Aspecifies the delay - how long to wait before wiggling the pitch,Bspecifies the speed - how fast to wiggle the pitch, andCspecifies the amplitude - how much to wiggle the pitch. If only two numbers are given, A is assumed to be 0, and it is interpreted aspB,C. Turn off vibrato withp0,0. (note for nerds:p0,0does result in$dfunlike in AddmusicK. it's free aram)- Manual pitch bends are fairly straightforward in ramekin, using
&and|^:
; bend b8 and then hold on c for the rest of the bar
b8 & >c4.^2
; multiple bends can be safely strung together
b8 & >c8 & d2
; to hold on a note and then do further bends, use a "break" character `|`
; this will hold steady on c, and then bend for an eighth note to the e.
b8 & c8^4 |^8 & e8
; to bend at the very end, use a zero-length note.
; this falls to g and then stops.
c4 & <g0
Panning Expression
Panning can be specified with the command yLX or yRX, where X is a number from 1-10. To reset panning to the center, use yC:
yR10 ; maximum pan right
yR2 ; tasteful pan right
yC ; pan center
yL2 ; tasteful pan left
yl10 ; maximum pan left
Fades / Automation
Many parameters, such as panning (yXX), tempo (tXX / #bpm:XX), volume (vXX), and global volume (wXX), can be faded in AddmusicK with special hex commands. Ramekin supports these with the "fade" operator \ (backslash). After any of these commands, put a backslash followed by a note length, including ties:
; fades to v+20 for the next half note tied to a quarter note
v+20\2^4.
It's easiest to think of \ itself as a note, which you are giving a duration. However, fades do not take any time themselves, rather cause the parameter to be changed over time from when they start. If you want to also continue a regular note after a fade, you can use a break command |, similar to bends:
; plays a c for a quarter note tied to a whole note, swelling in volume for
; a half note's time in the middle. the | prevents the tie from applying to the fade.
c4 v+20\2 |^1
Legato and quantization
To play notes in sequence without re-starting the note from the beginning, you can use a "legato tie", represented by ~:
a4~b4~>c4
This works by inserting AddmusicK's "legato toggle" command at appropriate times. To insert this toggle manually, use #legato/toggle - but be careful of using this command inside loops, as it can sometimes be ignored by AddmusicK, leaving you with settings that leave the toggle in an inconsistent state. Legato ties should always be safe inside loops.
To activate "sustain" mode, where notes are rekeyed but there is far less space between notes, use #sustain/global-toggle. AddmusicK will apply this setting to all channels at once. Again, this is a toggle, so be careful of activating this more than once unintentionally.
To activate staccato for a channel, you can change the "interval" (i.e. how long notes hold compared to their length) with iX, where X is a number 0-7. i7 results in the longest held notes, and i0 in the shortest. (If you've used qXY in AddmusicK before, note that Ramekin's syntax uses uYY for the second parameter here, so for example q7F in AddmusicK would be written i7u15 in Ramekin, and these can be used separately.)
Advanced Usage
Hex
For those who want to use AddmusicK features that are not currently supported by Ramekin, Ramekin still supports manual entry of hex values with $XX. While these can be made slightly easier to use with macros, I would very much appreciate it if you could open an issue here or contact me via SMWC or Discord if you find you are using these heavily.
Macros
To make your life easier, Ramekin implements a very similar macro ("replacement") system as AddmusicK - allowing you to define all manner of custom shortcuts for maximum efficiency and tweakability. (Note that Ramekin's implementation of macros is far more robust than AddmusicK's, and will not interrupt other tokens - e.g. a macro called F will not be applied to something like $EF).
A macro looks like this:
"MY_COOL_MACRO=@smwacousticbass o3 v150"
Now whenever you type MY_COOL_MACRO in a channel, it will be as if you typed @smwacousticbass o3 v150. This can be used to define all sorts of helpers, including some dynamics helpers:
"F=v+0" ; forte
"M=v-20" ; mezzo
"P=v-40" ; piano
Or drum instrument helpers:
"K=@smwkick o4"
"S=@smwsnare o4"
"H=@smwshaker o4"
Or anything else you might find helpful.
Custom Samples
If you have your own BRR-format samples you'd like to use that aren't on SMWCentral, Ramekin supports loading these by putting ./ at the start of the pack name:
; looks for a directory with BRR files in it next to the rmk file.
; tuning files will be picked up automatically if they exist.
#pack "./my-cool-pack"
Incompatibility Declarations
Often, a port will be too big to fit into ARAM without leaving out some sound effect samples. There are a few standard samples that are often left out, corresponding to incompatibilities with certain sound effects in the game. Ramekin allows you to specify these in the preamble, as:
#incompatible:clear ; declares incompatibility with "Stage Clear" sfx
#incompatible:bonus ; declares incompatibility with "Bonus End" music
#incompatible:starman ; declares incompatibility with star music
#incompatible:miss ; declares incompatibility with death sfx
#incompatible:pswitch ; declares incompatibility with p-switch music
#incompatible:gameover ; declares incompatibility with "Game Over" music
#incompatible:yoshi ; declares incompatibility with yoshi's sfx
#incompatible:thunder ; declares incompatibility with Thunder, Firework, and Blargg
Declaring any of these in the preamble will cause the relevant samples to be replaced with an empty sample, saving you ARAM, and will cause a note to be added to the README when generating the package.
Grace Notes
Often it's convenient to have little notes at the beginning or end of phrases that take up their own space, but it can be difficult to calculate the proper lengths of the rest of the notes so as not to desync. Grace note notation solves this problem: any note with a single ' quote after it will delete its length from the next note. For example:
; this figure has a length of one quarter note. the b- has a length of one quarter note *minus a 64th note*.
a64'b-4
; multiple grace notes can be used in a row, and they will take up the space of
; the next regular note. this figure also has the length of one quarter-note.
g+64'a64'b-4
To remove time from the back of a note, use a backtick (`):
; this figure has a length of two quarter notes, and the d+ will play exactly
; one 64th note before the beat, subtracting its time from the c.
c4`d+64e4
; similarly, multiple can be chained together. both grace notes subtract their
; time from the c.
c4`d64`d+64e4
Remote Commands
Remote commands allow for automatic running of certain automation commands at various hooks during play time. They can be specified in the preamble as:
(!my-remote-command)[ ... ]
and registered with various hooks:
(!my-remote-command) ; runs the command right now
(!my-remote-command #keyon) ; registers the command for note start
(!my-remote-command #release) ; registers the command for note release
(!my-remote-command #postattack 4) ; plays 4 ticks after keyon
(!my-remote-command #prerelease 4) ; plays 4 ticks before prerelease
You can unregister all remote commands with (!#off). (#!keyon-only) and (#!keyon-off) are also available, but have limited use.
A fundamental limitation of remote commands is they cannot contain notes or rests, or anything that would take up any amount of time - they can only be used for setting various channel settings.
Arpeggio and Glissando
AddmusicK supports automatic arpeggios - little phrases of equal-length notes that play relative to whatever note is being played. Ramekin's arpeggio syntax looks like:
#arp[32: 0 +1 -1] c1 #arp/off
What this is doing is setting an arpeggio at a speed of a 32nd note (any length specifier can be used here, including =N for a number of ticks, or tied lengths), which plays the original note, then one semitone up, then one semitone down, in a loop. Once the note c1 is done being played with this arpeggio, #arp/off unregisters the arpeggio, causing any notes afterwards to be played normally. A loop point can be specified within the arpeggio with /:
#arp[=5: 0 +1 / +2 +3]
Here, instead of looping back to the beginning when the end of the sequence is reached
Glissando is similar, except that only one offset is specified, which is the stride length of the glissando. Glissando also can only be activated during a note, before a tie.
c4 #gliss[=5: -2] ^1 ; glissando downwards by a stride of 2 semitones
Unlike #arp, #gliss does not need to be deactivated and will be off for the next note.
Understanding Intrinsic Note Properties
Many commands in Ramekin affect "intrinsic note properties", in other words, their value is "saved" in the notes that it effects. The full list of intrinsic note properties is:
- A note's octave (ex:
o3,>,<) - A note's transposition (ex:
h0,h+3,h-4) - A note's quantization/staccatto (ex:
q7,q4,q0) - A note's velocity (ex:
u0,u15)
For consistency and predictability, all four of these properties get reset to default values whenever a channel is declared, and whenever a new instrument is declared. The default values are o4q7u15h0. However, all of these defaults can be changed in the #instrument declaration, simply by including them at the end:
#instrument @my-instrument "My Sample" o3 h4 u7 q3
This will cause any declaration of @my-instrument to set these values instead of the defaults.
In particular, note that since these are intrinsic properties of notes, they are hard-coded into loops:
o3 [c1]
o2 * ; still plays in octave 3, since the octave is intrinsic to the note
h-3 * ; still plays the same note despite transposition
There are separate commands which affect transposition at the channel level which can be used here:
o3 [c1]
_-12 * ; dynamic transpose down 12 semitones
_-3 * ; dynamic transpose down 3 semitones
v100 * ; dynamic volume change
These commands all consume ARAM, unlike intrinsic properties which are "free", since they are included in the cost of inserting a note.
Thank you for trying out Ramekin!
If you find any bugs, please report them in the issue tracker here on Codeberg. If you have questions, design ideas, or complaints, or just want to chat, please feel free to ask anything in my discord. Cheers!
I know what I'm doing and would like to compile ramekin or run a development version
To compile this project, you will need to:
- make sure the repository is cloned recursively - if vendor/spct is empty you will need to
git submodule update --init --recursive. - Install crystal.
- Run
make. ./bin/ramekinwill be a dynamically-linked binary using local spct and libgme.- You can now run specs with
crystal spec.
Once you've compiled once, you should be able to also use the debugger. Run crystal i src/main.cr ... to run in Crystal interpreted mode, which will open a debugger on any debugger statement in the source. Please don't commit debugger statements unless they are in a check for flag?(:interpreted), as these will crash when running in compiled mode.
I really know what I'm doing and would like to make a (mostly)-static build
cross.mk is a Makefile designed to be run on arm64 mac (which I compile prod binaries on), that will (mostly)-statically cross compile ramekin for arm64 mac, x86_64 windows, and x86_64 linux. It currently only supports these three. See compile-cross.sh for examples of how to use this alternate Makefile.