Skip to content

dragoncoder047/tinya

Repository files navigation

TinyA

TinyA is a simple, versatile, and opinionated Javascript/Typescript library for playing a wide range of 8-bit videogame style sound effects and music.

TinyA is inspired by BeepBox, ZzFX, and ZzFXM, and but is not related to any of those, isn't entirely compatible, and is most certainly larger than ZzFXM (you wouldn't want to use TinyA in a js13k game even though it is small. It's not that small compared to ZzFXM.)

General topology

To be able to create the maximum variety of sound effects, TinyA implements a highly configurable audio pipeline, which can create many different sound effects.

Notation of Graph

yeah lol i'm still working on v2


Everything below this line is unimplemented and will be moved above it once it's added

so I joined the beepbox modding discord server and i guess if you have any questions about this repository you can ask them there

consider these TODO items

Special channels for music: BPM, envelopes/articulation, feed-through timing

(I typed this up as though I was going to implement it and then didn't. Oops! Here's to more free time...)

As TinyA is intended to create not only sound effects, but music, there are special invalid values placed at the beginning that will tell TinyA that the input channel timing array means something else.

  1. If the first element of the channel array is undefined and the rest are positive or undefined, this means the channel array is a tempo-based array. Instead of seconds for the time value and any other arbitrary value for the value, the time becomes beats and the output value becomes BPM, and the beats time value is relative to the current BPM. The "output" of this channel is then the beat count.

    Example:

    [
        , // undefined to put it in tempo mode
        -24, 120, // steady at 120 BPM for 24 beats
        4, 130, // linearly poco accelerando over 4 beats
        -3.75, 60, // suddent molto ritardando, 60 BPM, for 3.75 beats
        -.25, 3, // basically a fermata (hold last 16th node at 3 BPM)
        -32, 120 // a tempo for the rest of the song
    ]
  2. If the first element is a negative number (which would normally make no sense -- how could something last for negative time), it instead means that the channel is relative to the output of channel -(N-1). So for example, you could list the "conductor" channel first (at index 0) which controls the tempo and BPM, and song data channels could be relative to this (using beat count rather than wall time for timing); they would all start with -1 meaning channel 0.

Other ideas that haven't been well thought out

  • Optimize things, so that the "gain node" and "switch node" things are automatically recognized and the internal stack machine operation format can automatically skip updating the state of nodes that are known to not contribute to the final sample.

    • This may be harder than it looks because some nodes need to be continuously updated (which?)
  • Add "instrument instancing" where a sequence of instructions can be repeated for an array of values

    • The note data input track would produce these
    • Need to figure out how to create and destroy node templates and stuff
    • Need to figure out how to define the transition type and how the already-initialized instruments can be reused
      • "Normal" - strike each new note separately
      • "Slide" / "Slide in pattern" - nonzero pitch bend slide time, configurable max step distance, it just finds the closest note pairs in the transition and automatically extends the notes and then adds pitch slide instructions
      • "Interrupt" - reset effects envelopes except for ADSR, insert zero-length pitch slides
      • "Continue" - completely merges the notes and gate signals etc
  • Implement a generalized FM instrument macro (also once I figure out how BeepBox does it).

  • Have a node that converts MIDI note number into frequency

    • This would kind of fix it to 12-EDO because it would need extremely ugly decimals for microtonal that is not a multiple of 12
      • Auto-fraction may fix this, the fractions might work out nice. Or not.
  • Make the "compressed JSON" parser able to parse fractions like 1/16 so people can more easily hand-write stuff without having to break out a calculator (also .0625 is a tad longer than 1/16 and gets worse as the fraction gets smaller). Maybe also have the stringifier detect likely fractions and do this too. Relevant StackOverflow question

  • Make TinyA capable of streaming output (being able to play some sound before all samples are generated). Need to figure out how to reify buildSamples' internal state.

  • Be able to specify more than one channel value in the same array. This would be conducive to, say, encoding an entire track of a song (note pitch, articulation, dynamics, "muting" a horn, etc.) with the values at the same timestep right next to each other in the array, and the lengths are re-used, so stuff stays in sync.

    • This would mean that the single channel outputs an array, so the input reference needs to specify which index (possibly using the first number)
      • It could just be a macro that outputs things
      • Either that or the outputs of the channel have to be named
  • Maybe also make a ZzFXM importer and renderer.

  • Some more kinds of math nodes.

    • A selector node that takes N+1 inputs and uses the first % N to select which input (this could be used with a clock and integrator combination to do the arpeggiation thing from BeepBox)
    • Greater-than, less-than, etc
  • Maybe also have possible values going around be arrays of numbers

    • Have a node that can tell the length
    • This would simplify the BeepBox arpeggiation if the pitch channel output can output an array
  • 😃 😃 😃 Make a BeepBox fork that uses TinyA as a backend and lets people edit the instrument graph visually, and then export to some compressed-JSON string that can be sent to a dedicated renderer

    • DragonBox is not there yet.