First public contribution.
1 // Copyright (c) 1997-2009 Nokia Corporation and/or its subsidiary(-ies).
2 // All rights reserved.
3 // This component and the accompanying materials are made available
4 // under the terms of "Eclipse Public License v1.0"
5 // which accompanies this distribution, and is available
6 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
8 // Initial Contributors:
9 // Nokia Corporation - initial contribution.
14 // This file contains an implementation of the ToneGenerator interface
15 // that converts all tone generation requests in to sampled audio
16 // data to be played through the normal local sampled audio interface
20 #include "BtToneGenerator.h"
23 #include <mda/common/resource.h>
25 /******************************************************************************
28 * The following classes are used to generate simple frequency/duration tones,
29 * DTMF, and SymbianOS tone sequences in a WINS environment. The below code
30 * should only be considered for WINS.
31 ******************************************************************************/
33 // this defines the maximum possible amplitude allowed for TSineGen::SetFrequency()
34 const TInt KMaxAmplitude = 0x8000;
36 // default number of samples for trailing silence following a Tone
37 const TInt KDefaultTrailingSilenceSamples = 20;
40 // Sine tone generator
43 const TInt16 TSineGen::SineTable[KMaxSineTable] =
45 0, 804, 1607, 2410, 3211, 4011, 4807, 5601,
46 6392, 7179, 7961, 8739, 9511, 10278, 11038, 11792,
47 12539, 13278, 14009, 14732, 15446, 16150, 16845, 17530,
48 18204, 18867, 19519, 20159, 20787, 21402, 22004, 22594,
49 23169, 23731, 24278, 24811, 25329, 25831, 26318, 26789,
50 27244, 27683, 28105, 28510, 28897, 29268, 29621, 29955,
51 30272, 30571, 30851, 31113, 31356, 31580, 31785, 31970,
52 32137, 32284, 32412, 32520, 32609, 32678, 32727, 32757,
53 32767, 32757, 32727, 32678, 32609, 32520, 32412, 32284,
54 32137, 31970, 31785, 31580, 31356, 31113, 30851, 30571,
55 30272, 29955, 29621, 29268, 28897, 28510, 28105, 27683,
56 27244, 26789, 26318, 25831, 25329, 24811, 24278, 23731,
57 23169, 22594, 22004, 21402, 20787, 20159, 19519, 18867,
58 18204, 17530, 16845, 16150, 15446, 14732, 14009, 13278,
59 12539, 11792, 11038, 10278, 9511, 8739, 7961, 7179,
60 6392, 5601, 4807, 4011, 3211, 2410, 1607, 804,
61 0, -804, -1607, -2410, -3211, -4011, -4807, -5601,
62 -6392, -7179, -7961, -8739, -9511,-10278,-11038,-11792,
63 -12539,-13278,-14009,-14732,-15446,-16150,-16845,-17530,
64 -18204,-18867,-19519,-20159,-20787,-21402,-22004,-22594,
65 -23169,-23731,-24278,-24811,-25329,-25831,-26318,-26789,
66 -27244,-27683,-28105,-28510,-28897,-29268,-29621,-29955,
67 -30272,-30571,-30851,-31113,-31356,-31580,-31785,-31970,
68 -32137,-32284,-32412,-32520,-32609,-32678,-32727,-32757,
69 -32767,-32757,-32727,-32678,-32609,-32520,-32412,-32284,
70 -32137,-31970,-31785,-31580,-31356,-31113,-30851,-30571,
71 -30272,-29955,-29621,-29268,-28897,-28510,-28105,-27683,
72 -27244,-26789,-26318,-25831,-25329,-24811,-24278,-23731,
73 -23169,-22594,-22004,-21402,-20787,-20159,-19519,-18867,
74 -18204,-17530,-16845,-16150,-15446,-14732,-14009,-13278,
75 -12539,-11792,-11038,-10278, -9511, -8739, -7961, -7179,
76 -6392, -5601, -4807, -4011, -3211, -2410, -1607, -804,
79 const TInt16 TSineGen::IncTable[KMaxSineTable] =
81 804, 803, 803, 801, 800, 796, 794,
82 791, 787, 782, 778, 772, 767, 760, 754,
83 747, 739, 731, 723, 714, 704, 695, 685,
84 674, 663, 652, 640, 628, 615, 602, 590,
85 575, 562, 547, 533, 518, 502, 487, 471,
86 455, 439, 422, 405, 387, 371, 353, 334,
87 317, 299, 280, 262, 243, 224, 205, 185,
88 167, 147, 128, 108, 89, 69, 49, 30,
89 10, -10, -30, -49, -69, -89, -108, -128,
90 -147, -167, -185, -205, -224, -243, -262, -280,
91 -299, -317, -334, -353, -371, -387, -405, -422,
92 -439, -455, -471, -487, -502, -518, -533, -547,
93 -562, -575, -590, -602, -615, -628, -640, -652,
94 -663, -674, -685, -695, -704, -714, -723, -731,
95 -739, -747, -754, -760, -767, -772, -778, -782,
96 -787, -791, -794, -796, -800, -801, -803, -803,
97 -804, -804, -803, -803, -801, -800, -796, -794,
98 -791, -787, -782, -778, -772, -767, -760, -754,
99 -747, -739, -731, -723, -714, -704, -695, -685,
100 -674, -663, -652, -640, -628, -615, -602, -590,
101 -575, -562, -547, -533, -518, -502, -487, -471,
102 -455, -439, -422, -405, -387, -371, -353, -334,
103 -317, -299, -280, -262, -243, -224, -205, -185,
104 -167, -147, -128, -108, -89, -69, -49, -30,
105 -10, 10, 30, 49, 69, 89, 108, 128,
106 147, 167, 185, 205, 224, 243, 262, 280,
107 299, 317, 334, 353, 371, 387, 405, 422,
108 439, 455, 471, 487, 502, 518, 533, 547,
109 562, 575, 590, 602, 615, 628, 640, 652,
110 663, 674, 685, 695, 704, 714, 723, 731,
111 739, 747, 754, 760, 767, 772, 778, 782,
112 787, 791, 794, 796, 800, 801, 803, 803,
116 void TSineGen::SetFrequency(TInt aFrequency,TInt aAmplitude)
118 // Given the frequency set iStep.
119 // Reset iPosition to the equivalent of 0 degrees.
120 // In the special case of aFrequency==4KHz set iPosition to 90 degrees.
124 if (aAmplitude>(1<<15))
126 else if (aAmplitude<-(1<<15))
129 iAmplitude=aAmplitude;
131 // There are 256 entries in the sine table to traverse 360 degrees.
132 // The codec requires samples at a rate of 8000 per second.
133 // Thus for a 1Hz tone the step will be 256/8000 or 4/125.
134 // Now we need need the integer part of the result to end up in
135 // the MSB so we need to multiply by 2^24. This gives the formula
136 // step = (f*4*2^24)/125 or (f*2^26)/125.
137 // Our highest frequency is 4KHz so that the term (f*2^26) exceeds
138 // a 32 bit result by 4000/2^6 (2^6 is the number of significant bits
139 // left after a multiply by 2^26). i.e. 6 bits. We overcome this by
140 // having 6 bits less in the fraction, so the new formula becomes
141 // ((f*2^20)/125)*2^6. This still gives us 20 significant bits in the
145 iStep=(((TUint)aFrequency<<20)/125)<<6;
146 iPosition=(aFrequency==4000 ? 0x40000000 : 0);
149 TInt TSineGen::NextSample()
151 // Generate the next sample using linear interpolation
154 TUint pos=iPosition>>24;
155 TInt amp=((IncTable[pos]*((iPosition&0x00ffffff)>>20)));
158 amp=(amp*iAmplitude)>>15;
163 void TSineWave::Generate(TInt16* aDest,TInt aCount)
165 // Called when more samples need to be generated.
170 *aDest++=STATIC_CAST(TInt16,iGen1.NextSample()+iGen2.NextSample());
174 void TSineWave::SetFrequency(TInt aFrequency,TInt aAmplitude)
176 // Set to generate a single frequency
179 SetFrequency(aFrequency,aAmplitude,0,0);
182 void TSineWave::SetFrequency(TInt aFrequency1,TInt aAmplitude1,TInt aFrequency2,TInt aAmplitude2)
184 // Set to generate two frequencies
187 iGen1.SetFrequency(aFrequency1,aAmplitude1);
188 iGen2.SetFrequency(aFrequency2,aAmplitude2);
196 void TMdaToneGenerator::Configure(TInt aRate, TInt aChannels, TInt aRepeats, TInt aSilence, TInt aRampUp)
198 // Set up this tone generator to generate data at the desired sample rate
199 // and number of channels (typically mono/stereo)
203 iChannels = aChannels;
205 iRampUp = ETrue; // Default ramping to on as it is normally useful
208 iSilenceBetweenRepeats = aSilence;
209 iRampUpCount = aRampUp;
210 iRampUpLeft = aRampUp;
211 iAfterRepeatSilence = EFalse;
214 LOCAL_C void RampVolume(TInt16* aData,TInt aCount,TInt aStartVol,TInt aEndVol)
216 // Simple function to ramp down the volume of some samples
217 // Typically used to prevent "clicking" artifacts at the beginning/end of tones
220 TInt step = (aEndVol - aStartVol)/aCount;
223 TInt data = TInt(*aData) * aStartVol;
224 *aData++ = TInt16(data>>15);
229 TInt TMdaToneGenerator::FillBuffer(TDes8& aBuffer)
231 // Fill the supplied buffer with tone data
232 // Sets the buffer length to zero if there is no more data to play
233 // The buffer must have a max length of at least one sample * channels
234 // e.g. 2 bytes mono, 4 bytes stereo
237 ASSERT(aBuffer.MaxLength()>= (iChannels<<1));
242 TInt used = 0; // Data used
243 TInt avail = aBuffer.Length(); // Data filled
244 TInt count = 0; // Data to be converted
245 TBool rampUp = EFalse;
248 fill.Set(aBuffer); // Pointer to data left to be filled
251 // The rest of this function will loop around continually until the buffer
252 // is filled or there is no more data to play
256 silence = EFalse; // Reset
257 if (iSamplesLeft == 0)
259 if (iTrailingSilence == 0)
261 TInt error = GetNextTone();
266 if ((iSamplesLeft==0)&&(iTrailingSilence==0))
268 if ((iSilenceBetweenRepeats)&&(!iAfterRepeatSilence))
270 iTrailingSilence = iSilenceBetweenRepeats;
271 iAfterRepeatSilence = ETrue;
276 if ((iRepeats>0)||(iRepeats==KMdaRepeatForever))
278 iAfterRepeatSilence = EFalse;
294 samples = iTrailingSilence;
298 samples = iSamplesLeft;
300 count = Min(samples,avail>>1);
301 fill.SetLength(count<<1);
305 iSineWave.Generate(REINTERPRET_CAST(TInt16*,&fill[0]),count);
307 { // Ramp up volume at beginning of tone
308 const TInt KRampUpSamples = 50;
310 { // Fade in first few samples
311 TInt fadeInLength = Min(Min(KRampUpSamples,iSamplesLeft),(fill.Length()>>1));
312 RampVolume(CONST_CAST(TInt16*,REINTERPRET_CAST(const TInt16*,(&fill[0]))),fadeInLength,0,1<<15);
316 { // Ramp down volume at end of tone
317 const TInt KRampDownSamples = 50;
318 if ((iSamplesLeft-count) < KRampDownSamples)
319 { // Fade out last few samples
320 TInt fadeOutLength = Min(Min(KRampDownSamples,iSamplesLeft),(fill.Length()>>1));
321 RampVolume(CONST_CAST(TInt16*,REINTERPRET_CAST(const TInt16*,(&(fill.Right(fadeOutLength<<1))[0]))),fadeOutLength,1<<15,0);
324 iSamplesLeft -= count;
327 { // Generate silence
328 fill.FillZ(count<<1);
329 iTrailingSilence -= count;
334 fill.Shift(count<<1);
336 if (avail>(iChannels<<1))
341 aBuffer.SetLength(used);
343 // Do any ramp up that is required
346 TInt words = iRampUpLeft * iChannels;
347 words = Min(words,used>>1);
348 if (words>0) // In case buffer has zero length...
350 TInt left = iRampUpLeft * iChannels;
351 TInt rampup = iRampUpCount * iChannels;
352 iRampUpLeft -= words/iChannels;
353 TInt16* sample = REINTERPRET_CAST(TInt16*,&aBuffer[0]);
356 *sample++ = STATIC_CAST(TInt16,(TInt32(*sample)*(rampup-(left--)))/rampup);
364 TInt TMdaToneGenerator::DurationToSamples(const TTimeIntervalMicroSeconds& aDuration)
366 // Convert the given duration to a sample count using the current settings
369 const TInt64 KTInt64OneMilion = 1000000;
371 // Calculate duration as samples
372 TInt64 microSeconds(aDuration.Int64()); // MSVC doesn't like "aDuration.Int64()" in line below
373 TInt64 dur = ((TInt64(iRate) * TInt64(iChannels) * microSeconds) / KTInt64OneMilion);
375 return KMaxTInt; // Ridiculous!
381 // TMdaSimpleToneGenerator
384 void TMdaSimpleToneGenerator::Reset()
389 void TMdaSimpleToneGenerator::SetFrequencyAndDuration(TInt aFrequency, const TTimeIntervalMicroSeconds& aDuration)
391 // Store the frequency and duration of the specified sine tone
394 iFrequency = aFrequency;
395 iDuration = aDuration;
399 TInt TMdaSimpleToneGenerator::GetNextTone()
401 // Simple implementation - just sets the supplied frequency and duration
404 // This class only plays one tone for the specified duration
407 iSamplesLeft = I64LOW((iDuration.Int64() * TInt64(iRate))/1000000);
408 iSineWave.SetFrequency(iFrequency,1<<14);
410 iTrailingSilence = 20; // Just to stop clicking
416 // TMdaDualToneGenerator
419 void TMdaDualToneGenerator::Reset()
424 void TMdaDualToneGenerator::SetFrequencyAndDuration(TInt aFrequencyOne, TInt aFrequencyTwo, const TTimeIntervalMicroSeconds& aDuration)
426 // Store the frequencies and duration of the specified dual tone
427 iFrequencyOne = aFrequencyOne;
428 iFrequencyTwo = aFrequencyTwo;
429 iDuration = aDuration;
434 // This is called by TMdaToneGenerator::FillBuffer()
435 // to calculate the number of samples (iSamplesLeft) that will be needed
436 // for the tone to be played and to initialize the sine wave generator.
437 // If the tone has already been played, then leaves iSamplesLeft
438 // unmodified (should be zero) to indicate that it has finished.
440 TInt TMdaDualToneGenerator::GetNextTone()
442 // This class only plays one tone for the specified duration
445 iSamplesLeft = I64LOW((iDuration.Int64() * TInt64(iRate))/KOneMillionMicroSeconds);
446 iSineWave.SetFrequency(iFrequencyOne, KMaxAmplitude/2, iFrequencyTwo, KMaxAmplitude/2);
448 iTrailingSilence = KDefaultTrailingSilenceSamples; // Just to stop clicking
456 const TInt KRecalculateToneLengths = KMinTInt;
458 void TMdaDTMFGenerator::Reset()
463 void TMdaDTMFGenerator::SetToneDurations(const TTimeIntervalMicroSeconds32 aOn,
464 const TTimeIntervalMicroSeconds32 aOff,
465 const TTimeIntervalMicroSeconds32 aPause)
467 // Setup the DTMF tone durations
468 // aOn can be == -1 indicating should play first tone indefinately
471 ASSERT(aOn.Int() >=-1);
472 ASSERT(aOff.Int()>=0);
473 ASSERT(aPause.Int()>=0);
479 iOnSamples = KRecalculateToneLengths; // Must recalculate these later
482 void TMdaDTMFGenerator::SetString(const TDesC& aDTMFString)
484 // Store the DTMF string to be played
485 // No need to validate it as it will already have been checked
489 iDTMFString = &aDTMFString;
492 const TUint8 KDtmfVolumeTable[4][4]=
494 // Relative strengths to assign to different DTMF tones
496 // This is only important if DTMFs are being played through a speaker
497 // and need to be machine-recognisable. This table compensates for frequency
498 // drop-off in the speaker and can boost the relative volume of some
499 // frequencies so they are still within tolerance.
501 // The values normally need to be determined using a frequency analyser on
504 // Each column == same low frequency (697, 770, 852, 941 Hz)
505 // Each row == same high frequency (1209, 1336, 1477, 1633 Hz)
507 // The value are interpreted as ratios:
509 // 7f == 50% low, 50% high
519 const TUint8 KDtmfTone697=0x0;
520 const TUint8 KDtmfTone770=0x1;
521 const TUint8 KDtmfTone852=0x2;
522 const TUint8 KDtmfTone941=0x3;
524 const TUint8 KDtmfTone1209=0x00;
525 const TUint8 KDtmfTone1336=0x10;
526 const TUint8 KDtmfTone1477=0x20;
527 const TUint8 KDtmfTone1633=0x30;
529 const TUint8 KDtmfToneTable[16]=
531 KDtmfTone941|KDtmfTone1336,//0
532 KDtmfTone697|KDtmfTone1209,//1
533 KDtmfTone697|KDtmfTone1336,//2
534 KDtmfTone697|KDtmfTone1477,//3
535 KDtmfTone770|KDtmfTone1209,//4
536 KDtmfTone770|KDtmfTone1336,//5
537 KDtmfTone770|KDtmfTone1477,//6
538 KDtmfTone852|KDtmfTone1209,//7
539 KDtmfTone852|KDtmfTone1336,//8
540 KDtmfTone852|KDtmfTone1477,//9
542 KDtmfTone697|KDtmfTone1633,//A
543 KDtmfTone770|KDtmfTone1633,//B
544 KDtmfTone852|KDtmfTone1633,//C
545 KDtmfTone941|KDtmfTone1633,//D
546 KDtmfTone941|KDtmfTone1209,//E or *
547 KDtmfTone941|KDtmfTone1477,//F or #
550 TInt TMdaDTMFGenerator::GetNextTone()
552 // Setup frequency/duration/silence settings for next DTMF tone
553 // Supported characters are 0-9 A-F * # , and any kind of white space
556 TBool onlyPlayFirstTone = EFalse;
558 if (iOnSamples == KRecalculateToneLengths)
560 // Must recalculate tone durations as samples
562 // Handle special case where tone on duration negative
563 // - meaning play first character indefinately
565 iOnSamples = DurationToSamples(TInt64(iOn.Int()));
568 onlyPlayFirstTone = ETrue;
572 iOffSamples = DurationToSamples(TInt64(iOff.Int()));
573 iPauseSamples = DurationToSamples(TInt64(iPause.Int()));
578 if (iChar==iDTMFString->Length())
579 return KErrNone; // Finished. Nothing to do
581 TInt highFrequency = 0;
583 TInt lowFrequency = 0;
587 TChar c((*iDTMFString)[iChar++]);
588 if ((TUint)c=='#' || (TUint)c=='*' || c.IsHexDigit())
601 tableIndex=(TUint)c-'0';
605 tableIndex=(TUint)c-'A'+10;
608 TInt high=KDtmfToneTable[tableIndex]&0xf0;
609 TInt low=KDtmfToneTable[tableIndex]&0x0f;
621 default://KDtmfTone1633:
636 default://KDtmfTone941:
641 const TUint8* dtmfVolumes=&KDtmfVolumeTable[0][0];
642 TInt volume=dtmfVolumes[((low)<<2)+(high)]<<7;
644 lowVolume = (1<<15)-volume;
646 iTrailingSilence = iOffSamples;
647 iSamplesLeft = iOnSamples;
649 else if ((TUint)c==',')
651 iTrailingSilence = iPauseSamples;
654 else if (c.IsSpace())
656 if (iChar < iDTMFString->Length())
662 if (iOnSamples < 0) // Play only first character for ever
664 iTrailingSilence = 0;
665 iSamplesLeft = iRate * iChannels; // One second of samples
666 iChar = 0; // Reset so this character is played again next time
668 if (!onlyPlayFirstTone)
671 // This is not the first time around so we should not
672 // reset the tone generator - it will already have the
673 // correct settings and setting them again would cause
674 // an audible discontinuity
679 iSineWave.SetFrequency(highFrequency,highVolume,lowFrequency,lowVolume);
684 // TMdaSequenceGenerator
688 // Sequence constants
691 //const TInt KMaxFixedSequenceStack=KMaxSequenceStack;//Max nesting level of FixedSequences * 2
693 const TInt16 KFixedSequenceSignatureOne='S'+('Q'<<8);
694 const TInt16 KFixedSequenceSignatureTwo='N'+('C'<<8);
697 const TInt KFixedSequenceFunctionReturn=-1;
698 const TInt KFixedSequenceFunctionStartLoop=-2;
699 const TInt KFixedSequenceFunctionEndLoop=-3;
701 void TMdaSequenceGenerator::Reset()
703 iInstructionPtr = REINTERPRET_CAST(const TInt16*,&((*iSequenceData)[0]));
704 iInstructionPtr += 2; // Skip signature
708 void TMdaSequenceGenerator::SetSequenceData(const TDesC8& aSequenceData)
710 // Store the sequence data to be played
711 // No need to validate it as it will already have been checked
714 iSequenceData = &aSequenceData;
715 iInstructionPtr = REINTERPRET_CAST(const TInt16*,&aSequenceData[0]);
716 iLastInstruction = iInstructionPtr + (iSequenceData->Length()>>1) - 1;
718 // These are asserts because this should not be called if signature not present
719 ASSERT(*iInstructionPtr == KFixedSequenceSignatureOne);
720 ASSERT(*(iInstructionPtr+1) == KFixedSequenceSignatureTwo);
722 iInstructionPtr += 2; // Skip signature
727 TInt TMdaSequenceGenerator::GetNextTone()
731 ASSERT(iInstructionPtr); // Sanity check
733 TInt ret = KRequestPending;
734 while (ret == KRequestPending)
736 if (iInstructionPtr > iLastInstruction)
738 else if (*iInstructionPtr<=0)
740 switch (*iInstructionPtr)
742 case KFixedSequenceFunctionReturn: // End of sequence
746 case KFixedSequenceFunctionStartLoop:
747 if (iStackIndex>2) // Validate - can only nest twice
749 else if ((iInstructionPtr+2) > iLastInstruction)
750 ret = KErrCorrupt; // Don't run off end of sequence
753 iStack[iStackIndex++]=(TInt)(iInstructionPtr+2);
754 iStack[iStackIndex++]=(TInt)*(iInstructionPtr+1);
759 case KFixedSequenceFunctionEndLoop:
760 if (iStackIndex==0) // Validate - must already be nested
764 if ((--iStack[iStackIndex-1])!=0)
765 iInstructionPtr=(TInt16*)iStack[iStackIndex-2];
774 default: // Bad sequence
780 if ((iInstructionPtr+5) > iLastInstruction)
781 ret = KErrCorrupt; // Don't run off end of sequence
784 iSamplesLeft = *iInstructionPtr++;
785 TInt freqOne = *iInstructionPtr++;
786 TInt volOne = *iInstructionPtr++;
787 TInt freqTwo = *iInstructionPtr++;
788 TInt volTwo = *iInstructionPtr++;
790 if ((volOne> 1<<15)||(volTwo > 1<<15))
794 iSineWave.SetFrequency(freqOne,volOne,freqTwo,volTwo);
803 // ---------------------------------
804 // Code to generate sine table files used by tone generator
805 // Optionally called from InitL()
806 // #define GENERATE_SINE_TABLES 1
807 #ifdef GENERATE_SINE_TABLES
808 LOCAL_C GenerateSineTableL()
810 _LIT(KSineFile,"sine.txt");
811 _LIT(KSineIncFile,"sineinc.txt");
814 file.Replace(MdaManager::Fs(),KSineFile,EFileWrite);
815 CleanupClosePushL(file);
818 file2.Replace(MdaManager::Fs(),KSineIncFile,EFileWrite);
819 CleanupClosePushL(file2);
821 const TReal pi=3.141592653589;
822 const TReal twopi=pi*2;
823 const TReal samples = 256.0;
824 const TReal step = twopi/samples;
826 TBuf8<128> sinebuffer;
827 TBuf8<128> incbuffer;
832 _LIT8(KFormat,"%6d,");
833 _LIT8(KNewLine,"\n");
835 for(TReal angle=0.0;angle<=(twopi-step);) // Copes with rounding errors
839 for (int i=0;i<8;i++)
841 User::LeaveIfError(Math::Sin(res,angle));
842 current = TInt(KMaxTInt16*res);
843 sinebuffer.AppendFormat(KFormat,current);
844 if (last != KMaxTInt)
845 incbuffer.AppendFormat(KFormat,current-last);
851 sinebuffer.Append(KNewLine);
852 incbuffer.Append(KNewLine);
853 file.Write(sinebuffer);
854 file2.Write(incbuffer);
857 // Write fine difference to incbuffer - differnece between first and last
859 incbuffer.AppendFormat(KFormat,first-last);
860 incbuffer.Append(KNewLine);
861 file2.Write(incbuffer);
863 CleanupStack::PopAndDestroy(2);
866 //-------------------------------