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
151 lines
4.8 KiB
C
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;
|
|
}
|