2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
9 * Extract DTMF signals from 16 bit PCM audio
11 * Originally written by Poul-Henning Kamp <phk@freebsd.org>
12 * Made into a C++ class by Roger Hardiman <roger@freebsd.org>, January 2002
15 * Revision 1.18 2007/07/19 08:10:32 csoutheren
16 * Add detection of CNG
18 * Revision 1.17 2006/12/13 04:56:03 csoutheren
19 * Applied 1613270 - fixed for dtmfEncoder
20 * Thanks to Frederic Heem
22 * Revision 1.16 2006/11/11 15:23:37 hfriederich
23 * Use correct GetSize() to avoid allocation problems
25 * Revision 1.15 2006/10/25 08:18:22 rjongbloed
26 * Major upgrade of tone generation subsystem.
28 * Revision 1.14 2005/11/30 12:47:41 csoutheren
29 * Removed tabs, reformatted some code, and changed tags for Doxygen
31 * Revision 1.13 2005/01/25 06:35:27 csoutheren
32 * Removed warnings under MSVC
34 * Revision 1.12 2004/11/17 10:13:14 csoutheren
35 * Fixed compilation with gcc 4.0.0
37 * Revision 1.11 2004/09/09 23:50:49 csoutheren
38 * Fixed problem with duplicate definition of sinetab causing problems
40 * Revision 1.10 2004/09/09 05:23:38 dereksmithies
41 * Add utility function to report on dtmf characters used.
43 * Revision 1.9 2004/09/09 04:22:46 csoutheren
44 * Added sine table for DTMF encoder
46 * Revision 1.8 2004/09/09 04:00:01 csoutheren
47 * Added DTMF encoding functions
49 * Revision 1.7 2003/03/17 07:39:25 robertj
50 * Fixed possible invalid value causing DTMF detector to crash.
52 * Revision 1.6 2002/02/20 02:59:34 yurik
53 * Added end of line to trace statement
55 * Revision 1.5 2002/02/12 10:21:56 rogerh
56 * Stop sending '?' when a bad DTMF tone is detected.
58 * Revision 1.4 2002/01/24 11:14:45 rogerh
59 * Back out robert's change. It did not work (no sign extending)
60 * and replace it with a better solution which should be happy on both big
61 * endian and little endian systems.
63 * Revision 1.3 2002/01/24 10:40:17 rogerh
70 #pragma implementation "dtmf.h"
74 #include <ptclib/dtmf.h>
78 /* Integer math scaling factor */
81 /* This is the Q of the filter (pole radius) */
84 #define P2 ((int)(POLRAD*POLRAD*FSC))
88 PDTMFDecoder::PDTMFDecoder()
90 // Initialise the class
92 for (kk
= 0; kk
< NumTones
; kk
++) {
93 y
[kk
] = h
[kk
] = k
[kk
] = 0;
100 for (i
= 0; i
< 256; i
++) {
104 /* We encode the tones in 8 bits, translate those to symbol */
105 key
[0x11] = '1'; key
[0x12] = '4'; key
[0x14] = '7'; key
[0x18] = '*';
106 key
[0x21] = '2'; key
[0x22] = '5'; key
[0x24] = '8'; key
[0x28] = '0';
107 key
[0x41] = '3'; key
[0x42] = '6'; key
[0x44] = '9'; key
[0x48] = '#';
108 key
[0x81] = 'A'; key
[0x82] = 'B'; key
[0x84] = 'C'; key
[0x88] = 'D';
110 /* The frequencies we're trying to detect */
111 /* These are precalculated to save processing power */
112 /* static int dtmf[9] = {697, 770, 852, 941, 1209, 1336, 1477, 1633, 1100}; */
113 /* p1[kk] = (-cos(2 * 3.141592 * dtmf[kk] / 8000.0) * FSC) */
114 p1
[0] = -3497; p1
[1] = -3369; p1
[2] = -3212; p1
[3] = -3027;
115 p1
[4] = -2384; p1
[5] = -2040; p1
[6] = -1635; p1
[7] = -1164;
120 PString
PDTMFDecoder::Decode(const short * sampleData
, PINDEX numSamples
)
129 for (pos
= 0; pos
< numSamples
; pos
++) {
131 /* Read (and scale) the next 16 bit sample */
132 x
= ((int)(*sampleData
++)) / (32768/FSC
);
134 /* Input amplitude */
136 ia
+= (x
- ia
) / 128;
138 ia
+= (-x
- ia
) / 128;
142 for(kk
= 0; kk
< NumTones
; kk
++) {
145 c
= (P2
* (x
- k
[kk
])) / FSC
;
147 f
= (p1
[kk
] * (d
- h
[kk
])) / FSC
;
152 /* Detect and Average */
154 y
[kk
] += (n
- y
[kk
]) / 64;
156 y
[kk
] += (-n
- y
[kk
]) / 64;
159 if (y
[kk
] > FSC
/10 && y
[kk
] > ia
) {
167 /* Hysteresis and noise supressor */
171 } else if (nn
++ == 520) {
174 PTRACE(3,"DTMF\tDetected '" << key
[s
] << "' in PCM-16 stream");
178 else if (s
== 0x100) {
179 PTRACE(3,"DTMF\tDetected CNG in PCM-16 stream");
187 ////////////////////////////////////////////////////////////////////////////////////////////
189 static int sine(int angle
)
191 static int sinArray
[PTones::MaxFrequency
] = {
192 0,0,1,2,3,3,4,5,6,7,7,8,9,10,10,11,12,13,14,14,15,16,17,18,18,19,20,21,21,22,
193 23,24,25,25,26,27,28,29,29,30,31,32,32,33,34,35,36,36,37,38,39,40,40,41,42,43,43,44,45,46,
194 47,47,48,49,50,51,51,52,53,54,54,55,56,57,58,58,59,60,61,62,62,63,64,65,65,66,67,68,69,69,
195 70,71,72,72,73,74,75,76,76,77,78,79,80,80,81,82,83,83,84,85,86,87,87,88,89,90,90,91,92,93,
196 94,94,95,96,97,98,98,99,100,101,101,102,103,104,105,105,106,107,108,108,109,110,111,112,112,113,114,115,115,116,
197 117,118,119,119,120,121,122,122,123,124,125,126,126,127,128,129,130,130,131,132,133,133,134,135,136,137,137,138,139,140,
198 140,141,142,143,144,144,145,146,147,147,148,149,150,151,151,152,153,154,154,155,156,157,157,158,159,160,161,161,162,163,
199 164,164,165,166,167,168,168,169,170,171,171,172,173,174,175,175,176,177,178,178,179,180,181,181,182,183,184,185,185,186,
200 187,188,188,189,190,191,192,192,193,194,195,195,196,197,198,198,199,200,201,202,202,203,204,205,205,206,207,208,208,209,
201 210,211,212,212,213,214,215,215,216,217,218,218,219,220,221,221,222,223,224,225,225,226,227,228,228,229,230,231,231,232,
202 233,234,234,235,236,237,238,238,239,240,241,241,242,243,244,244,245,246,247,247,248,249,250,250,251,252,253,254,254,255,
203 256,257,257,258,259,260,260,261,262,263,263,264,265,266,266,267,268,269,269,270,271,272,272,273,274,275,275,276,277,278,
204 278,279,280,281,282,282,283,284,285,285,286,287,288,288,289,290,291,291,292,293,294,294,295,296,297,297,298,299,300,300,
205 301,302,303,303,304,305,306,306,307,308,309,309,310,311,312,312,313,314,314,315,316,317,317,318,319,320,320,321,322,323,
206 323,324,325,326,326,327,328,329,329,330,331,332,332,333,334,335,335,336,337,337,338,339,340,340,341,342,343,343,344,345,
207 346,346,347,348,349,349,350,351,352,352,353,354,354,355,356,357,357,358,359,360,360,361,362,363,363,364,365,365,366,367,
208 368,368,369,370,371,371,372,373,373,374,375,376,376,377,378,379,379,380,381,381,382,383,384,384,385,386,387,387,388,389,
209 389,390,391,392,392,393,394,394,395,396,397,397,398,399,400,400,401,402,402,403,404,405,405,406,407,407,408,409,410,410,
210 411,412,412,413,414,415,415,416,417,417,418,419,420,420,421,422,422,423,424,425,425,426,427,427,428,429,430,430,431,432,
211 432,433,434,434,435,436,437,437,438,439,439,440,441,442,442,443,444,444,445,446,446,447,448,449,449,450,451,451,452,453,
212 453,454,455,456,456,457,458,458,459,460,460,461,462,463,463,464,465,465,466,467,467,468,469,470,470,471,472,472,473,474,
213 474,475,476,476,477,478,478,479,480,481,481,482,483,483,484,485,485,486,487,487,488,489,489,490,491,492,492,493,494,494,
214 495,496,496,497,498,498,499,500,500,501,502,502,503,504,504,505,506,507,507,508,509,509,510,511,511,512,513,513,514,515,
215 515,516,517,517,518,519,519,520,521,521,522,523,523,524,525,525,526,527,527,528,529,529,530,531,531,532,533,533,534,535,
216 535,536,537,537,538,539,539,540,541,541,542,543,543,544,545,545,546,547,547,548,549,549,550,550,551,552,552,553,554,554,
217 555,556,556,557,558,558,559,560,560,561,562,562,563,564,564,565,565,566,567,567,568,569,569,570,571,571,572,573,573,574,
218 575,575,576,576,577,578,578,579,580,580,581,582,582,583,583,584,585,585,586,587,587,588,589,589,590,590,591,592,592,593,
219 594,594,595,596,596,597,597,598,599,599,600,601,601,602,602,603,604,604,605,606,606,607,607,608,609,609,610,611,611,612,
220 612,613,614,614,615,616,616,617,617,618,619,619,620,620,621,622,622,623,624,624,625,625,626,627,627,628,628,629,630,630,
221 631,631,632,633,633,634,635,635,636,636,637,638,638,639,639,640,641,641,642,642,643,644,644,645,645,646,647,647,648,648,
222 649,650,650,651,651,652,653,653,654,654,655,655,656,657,657,658,658,659,660,660,661,661,662,663,663,664,664,665,666,666,
223 667,667,668,668,669,670,670,671,671,672,673,673,674,674,675,675,676,677,677,678,678,679,679,680,681,681,682,682,683,683,
224 684,685,685,686,686,687,687,688,689,689,690,690,691,691,692,693,693,694,694,695,695,696,697,697,698,698,699,699,700,700,
225 701,702,702,703,703,704,704,705,705,706,707,707,708,708,709,709,710,710,711,712,712,713,713,714,714,715,715,716,717,717,
226 718,718,719,719,720,720,721,721,722,723,723,724,724,725,725,726,726,727,727,728,728,729,730,730,731,731,732,732,733,733,
227 734,734,735,735,736,736,737,738,738,739,739,740,740,741,741,742,742,743,743,744,744,745,745,746,746,747,748,748,749,749,
228 750,750,751,751,752,752,753,753,754,754,755,755,756,756,757,757,758,758,759,759,760,760,761,761,762,762,763,763,764,764,
229 765,765,766,766,767,768,768,769,769,770,770,771,771,772,772,773,773,774,774,774,775,775,776,776,777,777,778,778,779,779,
230 780,780,781,781,782,782,783,783,784,784,785,785,786,786,787,787,788,788,789,789,790,790,791,791,792,792,793,793,793,794,
231 794,795,795,796,796,797,797,798,798,799,799,800,800,801,801,802,802,802,803,803,804,804,805,805,806,806,807,807,808,808,
232 809,809,809,810,810,811,811,812,812,813,813,814,814,814,815,815,816,816,817,817,818,818,819,819,819,820,820,821,821,822,
233 822,823,823,823,824,824,825,825,826,826,827,827,827,828,828,829,829,830,830,831,831,831,832,832,833,833,834,834,834,835,
234 835,836,836,837,837,837,838,838,839,839,840,840,840,841,841,842,842,843,843,843,844,844,845,845,846,846,846,847,847,848,
235 848,848,849,849,850,850,850,851,851,852,852,853,853,853,854,854,855,855,855,856,856,857,857,857,858,858,859,859,859,860,
236 860,861,861,861,862,862,863,863,863,864,864,865,865,865,866,866,867,867,867,868,868,869,869,869,870,870,870,871,871,872,
237 872,872,873,873,874,874,874,875,875,875,876,876,877,877,877,878,878,878,879,879,880,880,880,881,881,881,882,882,883,883,
238 883,884,884,884,885,885,885,886,886,887,887,887,888,888,888,889,889,889,890,890,891,891,891,892,892,892,893,893,893,894,
239 894,894,895,895,895,896,896,896,897,897,898,898,898,899,899,899,900,900,900,901,901,901,902,902,902,903,903,903,904,904,
240 904,905,905,905,906,906,906,907,907,907,908,908,908,909,909,909,910,910,910,911,911,911,912,912,912,913,913,913,913,914,
241 914,914,915,915,915,916,916,916,917,917,917,918,918,918,918,919,919,919,920,920,920,921,921,921,922,922,922,922,923,923,
242 923,924,924,924,925,925,925,925,926,926,926,927,927,927,928,928,928,928,929,929,929,930,930,930,930,931,931,931,932,932,
243 932,932,933,933,933,934,934,934,934,935,935,935,935,936,936,936,937,937,937,937,938,938,938,939,939,939,939,940,940,940,
244 940,941,941,941,941,942,942,942,942,943,943,943,944,944,944,944,945,945,945,945,946,946,946,946,947,947,947,947,948,948,
245 948,948,949,949,949,949,950,950,950,950,951,951,951,951,952,952,952,952,952,953,953,953,953,954,954,954,954,955,955,955,
246 955,956,956,956,956,956,957,957,957,957,958,958,958,958,958,959,959,959,959,960,960,960,960,960,961,961,961,961,962,962,
247 962,962,962,963,963,963,963,963,964,964,964,964,964,965,965,965,965,965,966,966,966,966,967,967,967,967,967,967,968,968,
248 968,968,968,969,969,969,969,969,970,970,970,970,970,971,971,971,971,971,972,972,972,972,972,972,973,973,973,973,973,973,
249 974,974,974,974,974,975,975,975,975,975,975,976,976,976,976,976,976,977,977,977,977,977,977,978,978,978,978,978,978,979,
250 979,979,979,979,979,980,980,980,980,980,980,980,981,981,981,981,981,981,981,982,982,982,982,982,982,983,983,983,983,983,
251 983,983,984,984,984,984,984,984,984,984,985,985,985,985,985,985,985,986,986,986,986,986,986,986,986,987,987,987,987,987,
252 987,987,987,988,988,988,988,988,988,988,988,989,989,989,989,989,989,989,989,989,990,990,990,990,990,990,990,990,990,990,
253 991,991,991,991,991,991,991,991,991,992,992,992,992,992,992,992,992,992,992,992,993,993,993,993,993,993,993,993,993,993,
254 993,994,994,994,994,994,994,994,994,994,994,994,994,995,995,995,995,995,995,995,995,995,995,995,995,995,995,996,996,996,
255 996,996,996,996,996,996,996,996,996,996,996,996,997,997,997,997,997,997,997,997,997,997,997,997,997,997,997,997,997,997,
256 998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,998,999,999,999,999,999,999,
257 999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,
258 999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999,999
261 int quadrant
= angle
/ PTones::MaxFrequency
;
262 int offset
= angle
% PTones::MaxFrequency
;
266 return sinArray
[offset
];
268 return sinArray
[PTones::MaxFrequency
-1-offset
];
270 return -sinArray
[offset
];
272 return -sinArray
[PTones::MaxFrequency
-1-offset
];
277 ////////////////////////////////////////////////////////////////////////
280 PTones::PTones(unsigned volume
):
282 masterVolume(volume
),
292 PTones::PTones(const PString
& descriptor
, unsigned volume
)
293 : masterVolume(volume
)
295 if (!Generate(descriptor
)) {
296 PTRACE(1,"DTMF\tCannot encode tone \"" << descriptor
<< '"');
301 bool PTones::Generate(const PString
& descriptor
)
303 PStringArray toneChunks
= descriptor
.Tokenise('/');
304 if (toneChunks
.IsEmpty())
307 for (PINDEX chunk
= 0; chunk
< toneChunks
.GetSize(); chunk
++) {
308 // split frequency and cadence
309 PINDEX pos
= toneChunks
[chunk
].Find(':');
310 if (pos
== P_MAX_INDEX
)
313 PString frequencyStr
= toneChunks
[chunk
].Left(pos
).Trim();
314 PString cadenceStr
= toneChunks
[chunk
].Mid(pos
+1).Trim();
316 if (cadenceStr
.IsEmpty())
319 // Do we have a volume?
320 unsigned volume
= 100;
321 if ((pos
= frequencyStr
.Find('%')) != P_MAX_INDEX
) {
322 volume
= frequencyStr
.Left(pos
).AsUnsigned();
323 if (volume
< 1 || volume
> 100)
325 frequencyStr
.Delete(0, pos
+1);
328 if (frequencyStr
.IsEmpty())
331 // Parse the frequencies
332 unsigned frequency1
, frequency2
;
334 if ((pos
= frequencyStr
.FindOneOf("+-x")) != P_MAX_INDEX
) {
335 frequency1
= frequencyStr
.Left(pos
).AsUnsigned();
336 frequency2
= frequencyStr
.Mid(pos
+1).AsUnsigned();
337 operation
= frequencyStr
[pos
];
340 frequency1
= frequency2
= frequencyStr
.AsUnsigned();
345 double duration
= cadenceStr
.AsReal();
348 if (!Generate(operation
, frequency1
, frequency2
, (unsigned)(duration
*1000), volume
))
351 char originalOperation
= operation
;
355 while ((pos
= cadenceStr
.Find('-', pos
)) != P_MAX_INDEX
) {
356 duration
= cadenceStr
.Mid(++pos
).AsReal();
357 if (duration
< 0 || duration
> 60)
360 if (!Generate(operation
, frequency1
, frequency2
, (unsigned)(duration
*1000), volume
))
363 // Alternate between the tone and silence
364 operation
= operation
== ' ' ? originalOperation
: ' ';
372 bool PTones::Generate(char operation
, unsigned frequency1
, unsigned frequency2
, unsigned milliseconds
, unsigned volume
)
374 if (lastOperation
!= operation
||
375 lastFrequency1
!= frequency1
||
376 lastFrequency2
!= frequency2
) {
377 lastOperation
= operation
;
378 lastFrequency1
= frequency1
;
379 lastFrequency2
= frequency2
;
387 return Juxtapose(frequency1
, frequency2
, milliseconds
, volume
);
390 return Modulate(frequency1
, frequency2
, milliseconds
, volume
);
393 return PureTone(frequency1
, milliseconds
, volume
);
396 return Silence(milliseconds
);
403 bool PTones::Juxtapose(unsigned frequency1
, unsigned frequency2
, unsigned milliseconds
, unsigned volume
)
405 if (frequency1
< MinFrequency
|| frequency1
> MaxFrequency
||
406 frequency2
< MinFrequency
|| frequency2
> MaxFrequency
)
409 // TODO this gived 8000 samples for 100 ms !!!
410 //unsigned samples = CalcSamples(milliseconds, frequency1, frequency2);
411 unsigned samples
= milliseconds
* SampleRate
/ 1000;
412 while (samples
-- > 0) {
413 int a1
= sine(angle1
);
414 int a2
= sine(angle2
);
416 AddSample((a1
+ a2
) / 2, volume
);
418 angle1
+= frequency1
;
419 if (angle1
>= SampleRate
)
420 angle1
-= SampleRate
;
422 angle2
+= frequency2
;
423 if (angle2
>= SampleRate
)
424 angle2
-= SampleRate
;
430 bool PTones::Modulate(unsigned frequency1
, unsigned modulator
, unsigned milliseconds
, unsigned volume
)
432 if (frequency1
< MinFrequency
|| frequency1
> MaxFrequency
|| modulator
< MinModulation
|| modulator
>= frequency1
/2)
435 unsigned samples
= CalcSamples(milliseconds
, frequency1
, modulator
);
437 while (samples
-- > 0) {
438 int a1
= sine(angle1
); // -999 to 999
439 int a2
= sine(angle2
); // -999 to 999
441 AddSample((a1
* (a2
+ SineScale
)) / SineScale
/ 2, volume
);
443 angle1
+= frequency1
;
444 if (angle1
>= SampleRate
)
445 angle1
-= SampleRate
;
448 if (angle2
>= SampleRate
)
449 angle2
-= SampleRate
;
455 bool PTones::PureTone(unsigned frequency1
, unsigned milliseconds
, unsigned volume
)
457 if (frequency1
< MinFrequency
|| frequency1
> MaxFrequency
)
460 unsigned samples
= CalcSamples(milliseconds
, frequency1
);
461 while (samples
-- > 0) {
462 AddSample(sine(angle1
), volume
);
464 angle1
+= frequency1
;
465 if (angle1
>= SampleRate
)
466 angle1
-= SampleRate
;
472 bool PTones::Silence(unsigned milliseconds
)
474 unsigned samples
= milliseconds
* SampleRate
/1000;
475 while (samples
-- > 0)
481 unsigned PTones::CalcSamples(unsigned ms
, unsigned f1
, unsigned f2
)
483 // firstly, find the minimum time to repeat the waveform
489 while (v1
*f2
!= v2
*f1
) {
497 // v1 repetitions of f1 == v2 repetitions of f2
498 //cout << v1 << " cycles of " << f1 << "hz = " << v2 << " samples of " << f2 << "hz" << endl;
500 // now find the number of times we need to repeat this to match the sampling rate
503 while (n1
*SampleRate
*v1
!= n2
*f1
) {
504 if (n1
*SampleRate
*v1
< n2
*f1
)
510 // v1 repetitions of t == v2 repetitions sample frequency
511 //cout << n1*v1 << " cycles at " << f1 << "hz = "
512 // << n1*v2 << " cycles at " << f2 << "hz = "
513 // << n2 << " samples at " << SampleRate << "hz" << endl;
515 // Make sure we round up the number of milliseconds to even multiple of cycles
516 return ms
== 0 ? n2
: ((ms
* SampleRate
/1000 + n2
- 1)/n2
*n2
);
520 void PTones::AddSample(int sample
, unsigned volume
)
522 // Sample added is value from -1000 to 1000, rescale to short range -32767 to +32767
523 PINDEX length
= GetSize();
526 sample
*= masterVolume
;
527 sample
/= SineScale
*100*100/SHRT_MAX
;
528 SetAt(length
, (short)sample
);
532 ////////////////////////////////////////////////////////////////////////
534 PDTMFEncoder::PDTMFEncoder(const char * dtmf
, unsigned milliseconds
) :
537 AddTone(dtmf
, milliseconds
);
540 PDTMFEncoder::PDTMFEncoder(char digit
, unsigned milliseconds
) :
543 AddTone(digit
, milliseconds
);
546 void PDTMFEncoder::AddTone(const char * str
, unsigned milliseconds
)
552 AddTone(*str
++, milliseconds
);
556 void PDTMFEncoder::AddTone(char digit
, unsigned milliseconds
)
558 // DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm
560 static unsigned const dtmfFreqs
[16][2] = {
579 digit
= (char)toupper(digit
);
581 if ('0' <= digit
&& digit
<= '9')
583 else if ('A' <= digit
&& digit
<= 'D')
584 index
= digit
+ 10 - 'A';
585 else if (digit
== '*')
587 else if (digit
== '#')
592 Generate('+', dtmfFreqs
[index
][0], dtmfFreqs
[index
][1], milliseconds
);
596 void PDTMFEncoder::AddTone(double f1
, double f2
, unsigned milliseconds
)
598 if (f1
> 0 && f1
< MaxFrequency
&& f2
> 0 && f2
< MaxFrequency
){
599 Generate('+', (unsigned)f1
, (unsigned)f2
, milliseconds
);
601 PAssertAlways(PInvalidParameter
);
606 char PDTMFEncoder::DtmfChar(PINDEX i
)
608 PAssert(i
< 16, "Only 16 dtmf symbols. Index too large");
610 static char dtmfSymbols
[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','*','#' };
611 return dtmfSymbols
[i
];
615 ////////////////////////////////////////////////////////////////////////////