bytebeat info + qna thread
BotB Academy Bulletins
 
 
234817
Level 23 Signalist
luna197
 
 
 
post #234817 :: 2026.03.13 5:00am :: edit 2026.03.14 4:07pm
  
  Quirby64, Collidy, petet, agargara, SRB2er, Lasertooth and Ebeedell liēkd this
feel free to ask questions or share tips about bytebeat! (preferably but not necessarily floatbeat-tailored, as the math for most stuff is usually simpler for that)

this post will also be edited to add useful info so people coming to the thread don't need to look for whether the question they have has already been asked/answered! i may also sometimes sprinkle in some of my own discoveries/techniques as well :3

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

tips for starting out:

i highly recommend using floatbeat as that will simplify a whole bunch of things later on (no need to center waveforms, avoid wraparound, etc.)
the dollchan editor
is currently the most widely-used and feature-packed editor for bytebeat, so unless you're more comfortable working using the greggman editor for whatever reason i suggest you stick to the former!
try to approach making music the way you would if you had to build your own music making program! so making an oscillator/sound generator, then a sequencer, mixer - it can be as simple as playing a sine wave at different pitches and having multiple simultaneous instances of that added together to play melodies and chords!

code snippets:

run certain code only at t=0 (useful for initializing persistent variables aka state):

t || (
// init code here
),

// regular code here


basic setup for audio effects that require state:

t || (
fx = [], // fx memory array

filter = (inp, cut) => (
mem = fx[fxi++], // grabs current effect's memory from the array and increments the memory pointer
// important: if the value at the slot is not an object/array, modifying mem will not modify the value in fx memory! in that case you would need to save the current effect pointer value (pre-increment) and write to fx[fxi] directly, like in the commented lines below:
// cfxi = fxi++,
// fx[cfxi] += 1,

// ...
)
),

d = t, // copy of t for use in effect code since actual t automatically evaluates to 0 for whatever reason

fxi = 0, // fx memory pointer, incremented after each effect use (function call)

// ...


a basic low-pass filter (first order):

t || (
SR = 48000, // sample rate (48000hz)
fx = [],

clamp = (x, a, b) => min(max(x, a), b), // "clamps" a value to fit between 2 numbers (lower bound - a, upper bound - b)

// the fx code for the filter! you don't need to understand how it works, but if you're curious - it initializes its "position" to 0 and exponentially decays towards the input
filter = (inp, cut) => (
cut = clamp(cut/SR*2, 0, 1),
cfxi = fxi++,
fx[cfxi] ??= 0,
out = fx[cfxi],
fx[cfxi] += (inp - out)*cut,
out
)
),

fxi = 0,

src = 1 - t/512%2, // saw wave with a period of 1024 samples (46.875hz)

filter(src, 2000) // filtered (2000hz cutoff) output!

// also, taking the original signal and subtracting a low-pass filtered version of it gives you a high-pass filtered version:
// src - filter(src, 2000)


a better low-pass filter (2nd order, steeper cutoff):

t || (
SR = 48000, // sample rate (48000hz)
fx = [],

clamp = (x, a, b) => min(max(x, a), b), // "clamps" a value to fit between 2 numbers (lower bound - a, upper bound - b)

// fx code for 2nd order filter, based on the impulse tracker filter implementation:
// https://wiki.multimedia.cx/index.php/Impulse_Tracker#Resonant_filters
filter2 = (inp, cut, res) => (
cut = clamp(cut, 0.001, SR/2),
buf = fx[fxi++] ??= [0, 0],

p = 10**(-res*3/320),
r = SR/(2*PI*cut),

e = r*r,
d = e + 2*p*r + 2*p,

out = (inp + (d + e - 1)*buf[0] - e*buf[1])/d,
buf[1] = buf[0],
buf[0] = out,
out
)
),

fxi = 0,

src = 1 - t/512%2, // saw wave with a period of 1024 samples (46.875hz)
src /= 1.5, // reducing saw wave volume by 1.5x

filter2(src, 500, 0) // filtered (500hz cutoff) output!


⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

note: this thread was mainly made for the bytebeat round(s) in smackdown of the signals but you can look for info or ask around in this thread during other battles as well! i'll also check what sort of things i should explain/demonstrate in the bytebeat guide i'm writing based on what people ask or are confused about
also this is technically a dupe thread since bytebeat general exists but that one is ancient and filled with a bunch of old discussion about stuff that doesn't really matter anymore (concerns about using functions and whatnot) so here we are
 
 
234841
Level 23 Chipist
Blast_Brothers
 
 
 
post #234841 :: 2026.03.13 7:01am
  
  agargara, fmixolydian, Xaser, SRB2er, Max Chaplin and Lasertooth liēkd this
I've 'open-sourced' my previous bytebeats. Hopefully they can serve as inspiration for other people.

Sepulchre

Slouch

Piece Stacker
 
 
234965
Level 6 Playa
fmixolydian
 
 
post #234965 :: 2026.03.15 1:47am
  
  agargara and luna197 liēkd this
for bytebeat1k, you can pack byte/word arrays into strings by encoding them into UTF-16 (i recommend using cyberchef
for this), and then extracting the data by using array.charCodeAt .
 
 
234990
Level 11 Signalist
eslashmachine
 
 
post #234990 :: 2026.03.15 10:32am :: edit 2026.03.15 10:33am
  
  agargara liēkd this
Minification tip: Using .charCodeAt or other operations constantly will take up a lot of space, so you can do this to shrink your code if it is used more than once:

`st=[string], st2=[string], st[c='charCodeAt']+st2[c]`
 
 

LOGIN or REGISTER to add your own comments!