1/* Copyright (c) 2004-2005, Oracle and/or its affiliates. All rights reserved.
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a
4 * copy of this software and associated documentation files (the "Software"),
5 * to deal in the Software without restriction, including without limitation
6 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 * and/or sell copies of the Software, and to permit persons to whom the
8 * Software is furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice (including the next
11 * paragraph) shall be included in all copies or substantial portions of the
12 * Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 */
22
23#ifdef HAVE_XORG_CONFIG_H
24#include <xorg-config.h>
25#endif
26
27#include <sys/audio.h>
28#include <sys/uio.h>
29#include <limits.h>
30#include <math.h>
31#include <xserver_poll.h>
32
33#include "xf86.h"
34#include "xf86Priv.h"
35#include "xf86_OSlib.h"
36
37#define BELL_RATE       48000   /* Samples per second */
38#define BELL_HZ         50      /* Fraction of a second i.e. 1/x */
39#define BELL_MS         (1000/BELL_HZ)  /* MS */
40#define BELL_SAMPLES    (BELL_RATE / BELL_HZ)
41#define BELL_MIN        3       /* Min # of repeats */
42
43#define AUDIO_DEVICE    "/dev/audio"
44
45void
46xf86OSRingBell(int loudness, int pitch, int duration)
47{
48    static short samples[BELL_SAMPLES];
49    static short silence[BELL_SAMPLES]; /* "The Sound of Silence" */
50    static int lastFreq;
51    int cnt;
52    int i;
53    int written;
54    int repeats;
55    int freq;
56    audio_info_t audioInfo;
57    struct iovec iov[IOV_MAX];
58    int iovcnt;
59    double ampl, cyclen, phase;
60    int audioFD;
61
62    if ((loudness <= 0) || (pitch <= 0) || (duration <= 0)) {
63        return;
64    }
65
66    lastFreq = 0;
67    memset(silence, 0, sizeof(silence));
68
69    audioFD = open(AUDIO_DEVICE, O_WRONLY | O_NONBLOCK);
70    if (audioFD == -1) {
71        xf86Msg(X_ERROR, "Bell: cannot open audio device \"%s\": %s\n",
72                AUDIO_DEVICE, strerror(errno));
73        return;
74    }
75
76    freq = pitch;
77    freq = min(freq, (BELL_RATE / 2) - 1);
78    freq = max(freq, 2 * BELL_HZ);
79
80    /*
81     * Ensure full waves per buffer
82     */
83    freq -= freq % BELL_HZ;
84
85    if (freq != lastFreq) {
86        lastFreq = freq;
87        ampl = 16384.0;
88
89        cyclen = (double) freq / (double) BELL_RATE;
90        phase = 0.0;
91
92        for (i = 0; i < BELL_SAMPLES; i++) {
93            samples[i] = (short) (ampl * sin(2.0 * M_PI * phase));
94            phase += cyclen;
95            if (phase >= 1.0)
96                phase -= 1.0;
97        }
98    }
99
100    repeats = (duration + (BELL_MS / 2)) / BELL_MS;
101    repeats = max(repeats, BELL_MIN);
102
103    loudness = max(0, loudness);
104    loudness = min(loudness, 100);
105
106#ifdef DEBUG
107    ErrorF("BELL : freq %d volume %d duration %d repeats %d\n",
108           freq, loudness, duration, repeats);
109#endif
110
111    AUDIO_INITINFO(&audioInfo);
112    audioInfo.play.encoding = AUDIO_ENCODING_LINEAR;
113    audioInfo.play.sample_rate = BELL_RATE;
114    audioInfo.play.channels = 2;
115    audioInfo.play.precision = 16;
116    audioInfo.play.gain = min(AUDIO_MAX_GAIN, AUDIO_MAX_GAIN * loudness / 100);
117
118    if (ioctl(audioFD, AUDIO_SETINFO, &audioInfo) < 0) {
119        xf86Msg(X_ERROR,
120                "Bell: AUDIO_SETINFO failed on audio device \"%s\": %s\n",
121                AUDIO_DEVICE, strerror(errno));
122        close(audioFD);
123        return;
124    }
125
126    iovcnt = 0;
127
128    for (cnt = 0; cnt <= repeats; cnt++) {
129        if (cnt == repeats) {
130            /* Insert a bit of silence so that multiple beeps are distinct and
131             * not compressed into a single tone.
132             */
133            iov[iovcnt].iov_base = (char *) silence;
134            iov[iovcnt++].iov_len = sizeof(silence);
135        }
136        else {
137            iov[iovcnt].iov_base = (char *) samples;
138            iov[iovcnt++].iov_len = sizeof(samples);
139        }
140        if ((iovcnt >= IOV_MAX) || (cnt == repeats)) {
141            written = writev(audioFD, iov, iovcnt);
142
143            if ((written < ((int) (sizeof(samples) * iovcnt)))) {
144                /* audio buffer was full! */
145
146                int naptime;
147
148                if (written == -1) {
149                    if (errno != EAGAIN) {
150                        xf86Msg(X_ERROR,
151                                "Bell: writev failed on audio device \"%s\": %s\n",
152                                AUDIO_DEVICE, strerror(errno));
153                        close(audioFD);
154                        return;
155                    }
156                    i = iovcnt;
157                }
158                else {
159                    i = ((sizeof(samples) * iovcnt) - written)
160                        / sizeof(samples);
161                }
162                cnt -= i;
163
164                /* sleep a little to allow audio buffer to drain */
165                naptime = BELL_MS * i;
166                xserver_poll(NULL, 0, naptime);
167
168                i = ((sizeof(samples) * iovcnt) - written) % sizeof(samples);
169                iovcnt = 0;
170                if ((written != -1) && (i > 0)) {
171                    iov[iovcnt].iov_base = ((char *) samples) + i;
172                    iov[iovcnt++].iov_len = sizeof(samples) - i;
173                }
174            }
175            else {
176                iovcnt = 0;
177            }
178        }
179    }
180
181    close(audioFD);
182    return;
183}
184