// 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 "types.h" #include "user.h" #include "fcntl.h" #include "audio.h" #include "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; }