(2023-04-18) ShellBeat is possible too, but...
----------------------------------------------
...with many caveats. At least when we want to make it work in Busybox-like
environments.

Yes, we all know that even the ash version in BB supports bitwise operators
and we can wrap formulas almost as they are defined in C into something like 
$((255&($OUR_FORMULA))) in a loop and be happy. However, as much as you're 
tempted to use native loops to create the stream and and printfs to convert 
the decimal output of the formula into hexadecimal and then output as raw 
bytes, please don't. Printfs will work but they are _extremely_ slow, 
especially in BB where printf isn't a shell builtin. If you need to generate 
an endless byte stream at the 8000 Hz rate and higher, you have to take 
another way. Luckily, BB (and every other POSIX environment) contains all 
the commands we need to do this. There will be another caveat but we'll get 
to it later.

So, let's split our task of expression-based sound generation into several
parts:

1. Generating an endless integer sequence starting from 0.
2. Calculating the formula.
3. Converting the formula result into a raw byte.
4. Passing the byte into the playback program.

Step 4 is essentially the same as used in AwkBeat, so I won't repeat myself.
Here, let's assume we have SoX installed and exported the variables 
SAMPLERATE=8000 and PLAYCMD="play -q -tu8 -r${SAMPLERATE} -", so we don't 
have to write this part fully in the further examples. Now, let's start from 
the beginning.

To generate an endless integer sequence, we could use the seq command as I
had shown in my initial post about ByteBeat, but the issue is it's not 
endless and we have to specify the maximum integer, which depends on the 
target platform and compile time configuration of the shell itself. 
Alternatively, we can find something that generates endless lines and 
something that numbers them. And the first thought would be to use something 
like yes '' | cat -n, but this also poses several problems: cat doesn't 
allow us to select which line number to start from and smaller line numbers 
are preformatted. Luckily, BB/POSIX environments also offer us a special 
command to number lines, nl, which we can parameterize to not have 
preformatting and to start from 0. So, the endless integer sequence 
generation part ultimately looks like this:

yes '' | nl -ba -v0 -w0

Now that we have a stream of integers from 0 to whatever, what's next? Next,
we need to calculate the formula. Let's assume we have it saved into the 
${FORMULA} variable and it's a string with a valid shell-compatible math 
expression that takes t as the single parameter. And how do we substitute 
the parameter in the string that came from the standard input? This is what 
the xargs command is for (that also allows us to supply the parameter name 
to substitute). A naive approach would be to directly write something like 
this:

yes '' | nl -ba -v0 -w0 | xargs -It echo "$((255&(${FORMULA})))"

But, if you run this, you'll find out that all the lines return a single
value (the result of the formula being calculated against 0). That's because 
the shell we're running this in does the substitution of the nonexistent 
variable t (cast to 0 in math shell expressions) even before it gets to 
xargs. That's why we need to escape the dollar sign. But then, we'll just 
get a bunch of shell expressions printed. In order to evaluate them, we need 
to turn them into commands that we can pass to the actual shell afterwards. 
So, the final variant of this stage will look like this:

yes '' | nl -ba -v0 -w0 | xargs -It echo "echo \$((255&(${FORMULA})))" | sh

So, we've got our byte stream calculated and printed in decimal, which is not
quite what we want, right? We can use a printf command instead of the inner 
echo but, as I already said, it will be extremely slow. Instead, let's use 
another commonly available command that will do the job for us, dc:

yes '' | nl -ba -v0 -w0 | xargs -It echo "echo \$((255&(${FORMULA})))P" | sh
| dc

Here, we append the P instruction to every number, which tells dc to output
the value from the stack top as a string if it's a string, or as raw bytes 
if it isn't. Our case is the latter. Finally, we just pipe this raw byte 
stream into our player, and this is the ready "ShellBeat" one-liner:

yes '' | nl -ba -v0 -w0 | xargs -It echo "echo \$((255&(${FORMULA})))P" | sh
| dc | $PLAYCMD > /dev/null

But here is another caveat: the P command is non-standard for dc. Yes, it's
present in GNU dc and properly configured Busybox builds, but there are some 
systems where it's just not there. In this case, we have to adjust our 
one-liner to make dc output our numbers in hexadecimal and also use sed to 
left-align the output to 2 digits, after which it's passed to our old friend 
xxd:

yes '' | nl -ba -v0 -w0 | xargs -It echo "echo 16o\$((255&(${FORMULA})))p" |
sh | dc | sed 's/\<[0-9A-F]\>/0&/' | xxd -r -p | $PLAYCMD > /dev/null

Or, if you consider writing "16o" every time an overhead, you can use this
version:

(echo '16o'; yes '' | nl -ba -v0 -w0 | xargs -It echo "echo
\$((255&(${FORMULA})))p" | sh) | dc | sed 's/\<[0-9A-F]\>/0&/' | xxd -r -p | 
$PLAYCMD > /dev/null

Regardless of which of the three one-liners suits you best, any of them is
much faster than using printfs to generate the output. But still, there are 
too many pipes and substitutions. Are they really necessary? Is there any 
way to just evaluate our formula directly in the current shell and then 
render its output with whatever tool we choose? Well, check this out:

(t=0; while true; do printf '%02X\n' "$((255&(${FORMULA})))"; t=$((t+1));
done) | xxd -r -p | $PLAYCMD > /dev/null

Wait, what? Didn't I just say that printfs are slow? Well, yes, they are
terribly slow when generating bytestreams directly. But here, we are 
generating the source for xxd, and we're doing it line by line. In this 
case, printf works just as fast as echo. And xxd then reconstructs the 
binary stream as quickly as it can. This is what I have actually used in my 
shellbeat.sh script published on the main hoi.st page along with AwkBeat.

Moral of the story: when there is more than one way to do it, keep exploring
every possible option until you find the simplest one.

--- Luxferre ---