Need actual offset space (bug #2076)
[asterisk/asterisk.git] / indications.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Tone Management
5  * 
6  * Copyright (C) 2002, Pauline Middelink
7  *
8  * Pauline Middelink <middelink@polyware.nl>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  *
13  * This set of function allow us to play a list of tones on a channel.
14  * Each element has two frequencies, which are mixed together and a
15  * duration. For silence both frequencies can be set to 0.
16  * The playtones can be given as a comma seperated string.
17  */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <math.h>                       /* For PI */
23 #include <asterisk/indications.h>
24 #include <asterisk/frame.h>
25 #include <asterisk/options.h>
26 #include <asterisk/channel.h>
27 #include <asterisk/logger.h>
28 #include <asterisk/lock.h>
29
30 struct playtones_item {
31         int freq1;
32         int freq2;
33         int duration;
34         int modulate;
35 };
36
37 struct playtones_def {
38         int vol;
39         int reppos;
40         int nitems;
41         int interruptible;
42         struct playtones_item *items;
43 };
44
45 struct playtones_state {
46         int vol;
47         int reppos;
48         int nitems;
49         struct playtones_item *items;
50         int npos;
51         int pos;
52         int origwfmt;
53         struct ast_frame f;
54         unsigned char offset[AST_FRIENDLY_OFFSET];
55         short data[4000];
56 };
57
58 static void playtones_release(struct ast_channel *chan, void *params)
59 {
60         struct playtones_state *ps = params;
61         if (chan) {
62                 ast_set_write_format(chan, ps->origwfmt);
63         }
64         if (ps->items) free(ps->items);
65         free(ps);
66 }
67
68 static void * playtones_alloc(struct ast_channel *chan, void *params)
69 {
70         struct playtones_def *pd = params;
71         struct playtones_state *ps = malloc(sizeof(struct playtones_state));
72         if (!ps)
73                 return NULL;
74         memset(ps, 0, sizeof(struct playtones_state));
75         ps->origwfmt = chan->writeformat;
76         if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
77                 ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format (write)\n", chan->name);
78                 playtones_release(NULL, ps);
79                 ps = NULL;
80         } else {
81                 ps->vol = pd->vol;
82                 ps->reppos = pd->reppos;
83                 ps->nitems = pd->nitems;
84                 ps->items = pd->items;
85         }
86         /* Let interrupts interrupt :) */
87         chan->writeinterrupt = pd->interruptible;
88         return ps;
89 }
90
91 static int playtones_generator(struct ast_channel *chan, void *data, int len, int samples)
92 {
93         struct playtones_state *ps = data;
94         struct playtones_item *pi;
95         int x;
96         /* we need to prepare a frame with 16 * timelen samples as we're 
97          * generating SLIN audio
98          */
99         len = samples * 2;
100         if (len > sizeof(ps->data) / 2 - 1) {
101                 ast_log(LOG_WARNING, "Can't generate that much data!\n");
102                 return -1;
103         }
104         memset(&ps->f, 0, sizeof(ps->f));
105
106         pi = &ps->items[ps->npos];
107         for (x=0;x<len/2;x++) {
108                 if (pi->modulate)
109                 /* Modulate 1st tone with 2nd, to 90% modulation depth */
110                 ps->data[x] = ps->vol * 2 * (
111                         sin((pi->freq1 * 2.0 * M_PI / 8000.0) * (ps->pos + x)) *
112                         (0.9 * fabs(sin((pi->freq2 * 2.0 * M_PI / 8000.0) * (ps->pos + x))) + 0.1)
113                         );
114                 else
115                         /* Add 2 tones together */
116                         ps->data[x] = ps->vol * (
117                                 sin((pi->freq1 * 2.0 * M_PI / 8000.0) * (ps->pos + x)) +
118                                 sin((pi->freq2 * 2.0 * M_PI / 8000.0) * (ps->pos + x))
119                         );
120         }
121         ps->f.frametype = AST_FRAME_VOICE;
122         ps->f.subclass = AST_FORMAT_SLINEAR;
123         ps->f.datalen = len;
124         ps->f.samples = samples;
125         ps->f.offset = AST_FRIENDLY_OFFSET;
126         ps->f.data = ps->data;
127         ps->f.delivery.tv_sec = 0;
128         ps->f.delivery.tv_usec = 0;
129         ast_write(chan, &ps->f);
130
131         ps->pos += x;
132         if (pi->duration && ps->pos >= pi->duration * 8) {      /* item finished? */
133                 ps->pos = 0;                                    /* start new item */
134                 ps->npos++;
135                 if (ps->npos >= ps->nitems) {                   /* last item? */
136                         if (ps->reppos == -1)                   /* repeat set? */
137                                 return -1;
138                         ps->npos = ps->reppos;                  /* redo from top */
139                 }
140         }
141         return 0;
142 }
143
144 static struct ast_generator playtones = {
145         alloc: playtones_alloc,
146         release: playtones_release,
147         generate: playtones_generator,
148 };
149
150 int ast_playtones_start(struct ast_channel *chan, int vol, const char *playlst, int interruptible)
151 {
152         char *s, *data = ast_strdupa(playlst); /* cute */
153         struct playtones_def d = { vol, -1, 0, 1, NULL};
154         char *stringp=NULL;
155         char *separator;
156         if (!data)
157                 return -1;
158         if (vol < 1)
159                 d.vol = 8192;
160
161         d.interruptible = interruptible;
162         
163         stringp=data;
164         /* the stringp/data is not null here */
165         /* check if the data is separated with '|' or with ',' by default */
166         if (strchr(stringp,'|'))
167                 separator = "|";
168         else
169                 separator = ",";
170         s = strsep(&stringp,separator);
171         while (s && *s) {
172                 int freq1, freq2, time, modulate=0;
173
174                 if (s[0]=='!')
175                         s++;
176                 else if (d.reppos == -1)
177                         d.reppos = d.nitems;
178                 if (sscanf(s, "%d+%d/%d", &freq1, &freq2, &time) == 3) {
179                         /* f1+f2/time format */
180                 } else if (sscanf(s, "%d+%d", &freq1, &freq2) == 2) {
181                         /* f1+f2 format */
182                         time = 0;
183                 } else if (sscanf(s, "%d*%d/%d", &freq1, &freq2, &time) == 3) {
184                         /* f1*f2/time format */
185                         modulate = 1;
186                 } else if (sscanf(s, "%d*%d", &freq1, &freq2) == 2) {
187                         /* f1*f2 format */
188                         time = 0;
189                         modulate = 1;
190                 } else if (sscanf(s, "%d/%d", &freq1, &time) == 2) {
191                         /* f1/time format */
192                         freq2 = 0;
193                 } else if (sscanf(s, "%d", &freq1) == 1) {
194                         /* f1 format */
195                         freq2 = 0;
196                         time = 0;
197                 } else {
198                         ast_log(LOG_WARNING,"%s: tone component '%s' of '%s' is no good\n",chan->name,s,playlst);
199                         return -1;
200                 }
201
202                 d.items = realloc(d.items,(d.nitems+1)*sizeof(struct playtones_item));
203                 if (d.items == NULL)
204                         return -1;
205                 d.items[d.nitems].freq1    = freq1;
206                 d.items[d.nitems].freq2    = freq2;
207                 d.items[d.nitems].duration = time;
208                 d.items[d.nitems].modulate = modulate;
209                 d.nitems++;
210
211                 s = strsep(&stringp,separator);
212         }
213
214         if (ast_activate_generator(chan, &playtones, &d)) {
215                 free(d.items);
216                 return -1;
217         }
218         return 0;
219 }
220
221 void ast_playtones_stop(struct ast_channel *chan)
222 {
223         ast_deactivate_generator(chan);
224 }
225
226 /*--------------------------------------------*/
227
228 struct tone_zone *tone_zones;
229 static struct tone_zone *current_tonezone;
230
231 /* Protect the tone_zones list (highly unlikely that two things would change
232  * it at the same time, but still! */
233 AST_MUTEX_DEFINE_EXPORTED(tzlock);
234
235 /* Set global indication country */
236 int ast_set_indication_country(const char *country)
237 {
238         if (country) {
239                 struct tone_zone *z = ast_get_indication_zone(country);
240                 if (z) {
241                         if (option_verbose > 2)
242                                 ast_verbose(VERBOSE_PREFIX_3 "Setting default indication country to '%s'\n",country);
243                         current_tonezone = z;
244                         return 0;
245                 }
246         }
247         return 1; /* not found */
248 }
249
250 /* locate tone_zone, given the country. if country == NULL, use the default country */
251 struct tone_zone *ast_get_indication_zone(const char *country)
252 {
253         struct tone_zone *tz;
254         int alias_loop = 0;
255
256         /* we need some tonezone, pick the first */
257         if (country == NULL && current_tonezone)
258                 return current_tonezone;        /* default country? */
259         if (country == NULL && tone_zones)
260                 return tone_zones;              /* any country? */
261         if (country == NULL)
262                 return 0;       /* not a single country insight */
263
264         if (ast_mutex_lock(&tzlock)) {
265                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
266                 return 0;
267         }
268         do {
269                 for (tz=tone_zones; tz; tz=tz->next) {
270                         if (strcasecmp(country,tz->country)==0) {
271                                 /* tone_zone found */
272                                 if (tz->alias && tz->alias[0]) {
273                                         country = tz->alias;
274                                         break;
275                                 }
276                                 ast_mutex_unlock(&tzlock);
277                                 return tz;
278                         }
279                 }
280         } while (++alias_loop<20 && tz);
281         ast_mutex_unlock(&tzlock);
282         if (alias_loop==20)
283                 ast_log(LOG_NOTICE,"Alias loop for '%s' forcefull broken\n",country);
284         /* nothing found, sorry */
285         return 0;
286 }
287
288 /* locate a tone_zone_sound, given the tone_zone. if tone_zone == NULL, use the default tone_zone */
289 struct tone_zone_sound *ast_get_indication_tone(const struct tone_zone *zone, const char *indication)
290 {
291         struct tone_zone_sound *ts;
292
293         /* we need some tonezone, pick the first */
294         if (zone == NULL && current_tonezone)
295                 zone = current_tonezone;        /* default country? */
296         if (zone == NULL && tone_zones)
297                 zone = tone_zones;              /* any country? */
298         if (zone == NULL)
299                 return 0;       /* not a single country insight */
300
301         if (ast_mutex_lock(&tzlock)) {
302                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
303                 return 0;
304         }
305         for (ts=zone->tones; ts; ts=ts->next) {
306                 if (strcasecmp(indication,ts->name)==0) {
307                         /* found indication! */
308                         ast_mutex_unlock(&tzlock);
309                         return ts;
310                 }
311         }
312         /* nothing found, sorry */
313         ast_mutex_unlock(&tzlock);
314         return 0;
315 }
316
317 /* helper function to delete a tone_zone in its entirety */
318 static inline void free_zone(struct tone_zone* zone)
319 {
320         while (zone->tones) {
321                 struct tone_zone_sound *tmp = zone->tones->next;
322                 free((void*)zone->tones->name);
323                 free((void*)zone->tones->data);
324                 free(zone->tones);
325                 zone->tones = tmp;
326         }
327         if (zone->ringcadance)
328                 free((void*)zone->ringcadance);
329         free(zone);
330 }
331
332 /*--------------------------------------------*/
333
334 /* add a new country, if country exists, it will be replaced. */
335 int ast_register_indication_country(struct tone_zone *zone)
336 {
337         struct tone_zone *tz,*pz;
338
339         if (ast_mutex_lock(&tzlock)) {
340                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
341                 return -1;
342         }
343         for (pz=NULL,tz=tone_zones; tz; pz=tz,tz=tz->next) {
344                 if (strcasecmp(zone->country,tz->country)==0) {
345                         /* tone_zone already there, replace */
346                         zone->next = tz->next;
347                         if (pz)
348                                 pz->next = zone;
349                         else
350                                 tone_zones = zone;
351                         /* if we are replacing the default zone, re-point it */
352                         if (tz == current_tonezone)
353                                 current_tonezone = zone;
354                         /* now free the previous zone */
355                         free_zone(tz);
356                         ast_mutex_unlock(&tzlock);
357                         return 0;
358                 }
359         }
360         /* country not there, add */
361         zone->next = NULL;
362         if (pz)
363                 pz->next = zone;
364         else
365                 tone_zones = zone;
366         ast_mutex_unlock(&tzlock);
367
368         if (option_verbose > 2)
369                 ast_verbose(VERBOSE_PREFIX_3 "Registered indication country '%s'\n",zone->country);
370         return 0;
371 }
372
373 /* remove an existing country and all its indications, country must exist.
374  * Also, all countries which are an alias for the specified country are removed. */
375 int ast_unregister_indication_country(const char *country)
376 {
377         struct tone_zone *tz, *pz = NULL, *tmp;
378         int res = -1;
379
380         if (ast_mutex_lock(&tzlock)) {
381                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
382                 return -1;
383         }
384         tz = tone_zones;
385         while (tz) {
386                 if (country==NULL ||
387                     (strcasecmp(country, tz->country)==0 ||
388                      strcasecmp(country, tz->alias)==0)) {
389                         /* tone_zone found, remove */
390                         tmp = tz->next;
391                         if (pz)
392                                 pz->next = tmp;
393                         else
394                                 tone_zones = tmp;
395                         /* if we are unregistering the default country, w'll notice */
396                         if (tz == current_tonezone) {
397                                 ast_log(LOG_NOTICE,"Removed default indication country '%s'\n",tz->country);
398                                 current_tonezone = NULL;
399                         }
400                         if (option_verbose > 2)
401                                 ast_verbose(VERBOSE_PREFIX_3 "Unregistered indication country '%s'\n",tz->country);
402                         free_zone(tz);
403                         if (tone_zones == tz)
404                                 tone_zones = tmp;
405                         tz = tmp;
406                         res = 0;
407                 }
408                 else {
409                         /* next zone please */
410                         pz = tz;
411                         tz = tz->next;
412                 }
413         }
414         ast_mutex_unlock(&tzlock);
415         return res;
416 }
417
418 /* add a new indication to a tone_zone. tone_zone must exist. if the indication already
419  * exists, it will be replaced. */
420 int ast_register_indication(struct tone_zone *zone, const char *indication, const char *tonelist)
421 {
422         struct tone_zone_sound *ts,*ps;
423
424         /* is it an alias? stop */
425         if (zone->alias[0])
426                 return -1;
427
428         if (ast_mutex_lock(&tzlock)) {
429                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
430                 return -2;
431         }
432         for (ps=NULL,ts=zone->tones; ts; ps=ts,ts=ts->next) {
433                 if (strcasecmp(indication,ts->name)==0) {
434                         /* indication already there, replace */
435                         free((void*)ts->name);
436                         free((void*)ts->data);
437                         break;
438                 }
439         }
440         if (!ts) {
441                 /* not there, we have to add */
442                 ts = malloc(sizeof(struct tone_zone_sound));
443                 if (!ts) {
444                         ast_log(LOG_WARNING, "Out of memory\n");
445                         ast_mutex_unlock(&tzlock);
446                         return -2;
447                 }
448                 ts->next = NULL;
449         }
450         ts->name = strdup(indication);
451         ts->data = strdup(tonelist);
452         if (ts->name==NULL || ts->data==NULL) {
453                 ast_log(LOG_WARNING, "Out of memory\n");
454                 ast_mutex_unlock(&tzlock);
455                 return -2;
456         }
457         if (ps)
458                 ps->next = ts;
459         else
460                 zone->tones = ts;
461         ast_mutex_unlock(&tzlock);
462         return 0;
463 }
464
465 /* remove an existing country's indication. Both country and indication must exist */
466 int ast_unregister_indication(struct tone_zone *zone, const char *indication)
467 {
468         struct tone_zone_sound *ts,*ps = NULL, *tmp;
469         int res = -1;
470
471         /* is it an alias? stop */
472         if (zone->alias[0])
473                 return -1;
474
475         if (ast_mutex_lock(&tzlock)) {
476                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
477                 return -1;
478         }
479         ts = zone->tones;
480         while (ts) {
481                 if (strcasecmp(indication,ts->name)==0) {
482                         /* indication found */
483                         tmp = ts->next;
484                         if (ps)
485                                 ps->next = tmp;
486                         else
487                                 zone->tones = tmp;
488                         free((void*)ts->name);
489                         free((void*)ts->data);
490                         free(ts);
491                         ts = tmp;
492                         res = 0;
493                 }
494                 else {
495                         /* next zone please */
496                         ps = ts;
497                         ts = ts->next;
498                 }
499         }
500         /* indication not found, goodbye */
501         ast_mutex_unlock(&tzlock);
502         return res;
503 }