SchOS/cmd/tone/main.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

151 lines
4.8 KiB
C

// tone -- write a sine wave to /dev/dsp.
//
// The smallest program that proves the audio path end to end: it asks the
// device what it can play, generates that, and writes it. If a machine makes
// this noise, every layer between a write() and a speaker is working.
//
// tone 440 Hz for one second
// tone 1000 1000 Hz for one second
// tone 1000 3 1000 Hz for three seconds
//
// Deliberately not a WAV player. A player has to parse a header, and a header
// is a second thing to get wrong while testing the first.
#include "core/types.h"
#include "uapi/user.h"
#include "uapi/fcntl.h"
#include "dev/audio.h"
#include "uapi/errno.h"
// A quarter-amplitude sine, from a table rather than a libm that does not
// exist here. 64 points is audibly a sine and arithmetically exact.
static const short SINE[64] = {
0, 803, 1605, 2404, 3196, 3980, 4753, 5511,
6254, 6977, 7679, 8357, 9008, 9630, 10220, 10777,
11297, 11780, 12222, 12623, 12980, 13293, 13559, 13778,
13951, 14075, 14151, 14178, 14156, 14085, 13965, 13797,
13581, 13318, 13009, 12655, 12257, 11818, 11338, 10820,
10266, 9679, 9060, 8413, 7740, 7044, 6329, 5597,
4851, 4096, 3334, 2569, 1804, 1043, 289, -453,
-1180, -1889, -2576, -3239, -3874, -4479, -5051, -5588
};
// lib/ has no stdio: write(2) is the whole of it, which is why every
// first-party program in cmd/ carries these three lines.
static void
say(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void *)s, n);
}
static void
sayn(int v)
{
char b[12];
int i = 0, j;
if (v == 0) { say("0"); return; }
if (v < 0) { say("-"); v = -v; }
while (v && i < 11) { b[i++] = (char)('0' + v % 10); v /= 10; }
for (j = i; j-- > 0; )
write(1, &b[j], 1);
}
static int
num(const char *s)
{
int v = 0;
while (*s >= '0' && *s <= '9')
v = v * 10 + (*s++ - '0');
return v;
}
int
main(int argc, char *argv[])
{
int hz = argc > 1 ? num(argv[1]) : 440;
int secs = argc > 2 ? num(argv[2]) : 1;
int rate = 48000, channels = 2, fmt = AFMT_S16_LE;
short buf[1024]; /* 512 stereo frames */
int fd, phase = 0;
if (hz < 20 || hz > 20000) {
say("tone: "); sayn(hz); say(" Hz is not audible\n");
return 1;
}
if (secs < 1 || secs > 30)
secs = 1;
fd = open("/dev/dsp", O_WRONLY);
if (fd < 0) {
say("tone: no /dev/dsp -- is there an audio device?\n");
return 1;
}
// Ask, then believe the answer. These are read/write on purpose: the
// device writes back what it actually set, and a program that ignores
// that plays at the wrong speed and blames the driver.
//
// The two failures are told apart, because they were not and the message
// was a lie: a machine with no sound card at all reported "the device will
// not take 16-bit samples", which sends you looking at the format code
// instead of at the empty PCI slot. The node exists whether or not any
// hardware is behind it -- that is deliberate, and it is why the *ioctl*
// is where the absence shows up.
int r = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
if (r == -ENODEV) {
say("tone: there is no audio device on this machine\n");
say(" (/dev/dsp exists on every machine; the hardware does not)\n");
close(fd);
return 1;
}
if (r < 0 || fmt != AFMT_S16_LE) {
say("tone: the device will not take 16-bit samples (ioctl said ");
sayn(r); say(")\n");
close(fd);
return 1;
}
ioctl(fd, SNDCTL_DSP_CHANNELS, &channels);
ioctl(fd, SNDCTL_DSP_SPEED, &rate);
say("tone: "); sayn(hz); say(" Hz for "); sayn(secs);
say(" s at "); sayn(rate); say(" Hz, "); sayn(channels);
say(" channel(s)\n");
// The phase step in 1/256ths of a table entry, so a 440 Hz tone at 48 kHz
// is not quantised into something a tuner would argue with.
int step = (hz * 64 * 256) / rate;
int frames_total = rate * secs;
while (frames_total > 0) {
int frames = frames_total > 512 ? 512 : frames_total;
int i;
for (i = 0; i < frames; i++) {
short s = SINE[(phase >> 8) & 63];
phase += step;
if (channels == 2) {
buf[i * 2] = s;
buf[i * 2 + 1] = s;
} else {
buf[i] = s;
}
}
int bytes = frames * channels * 2;
if (write(fd, buf, bytes) != bytes) {
say("tone: short write -- the device stopped taking audio\n");
close(fd);
return 1;
}
frames_total -= frames;
}
// Wait for it to finish rather than exiting on top of it. close() drains
// too, but saying so is the point of the ioctl existing.
ioctl(fd, SNDCTL_DSP_SYNC, 0);
close(fd);
return 0;
}