/src/aac/libSBRenc/src/resampler.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* ----------------------------------------------------------------------------- |
2 | | Software License for The Fraunhofer FDK AAC Codec Library for Android |
3 | | |
4 | | © Copyright 1995 - 2018 Fraunhofer-Gesellschaft zur Förderung der angewandten |
5 | | Forschung e.V. All rights reserved. |
6 | | |
7 | | 1. INTRODUCTION |
8 | | The Fraunhofer FDK AAC Codec Library for Android ("FDK AAC Codec") is software |
9 | | that implements the MPEG Advanced Audio Coding ("AAC") encoding and decoding |
10 | | scheme for digital audio. This FDK AAC Codec software is intended to be used on |
11 | | a wide variety of Android devices. |
12 | | |
13 | | AAC's HE-AAC and HE-AAC v2 versions are regarded as today's most efficient |
14 | | general perceptual audio codecs. AAC-ELD is considered the best-performing |
15 | | full-bandwidth communications codec by independent studies and is widely |
16 | | deployed. AAC has been standardized by ISO and IEC as part of the MPEG |
17 | | specifications. |
18 | | |
19 | | Patent licenses for necessary patent claims for the FDK AAC Codec (including |
20 | | those of Fraunhofer) may be obtained through Via Licensing |
21 | | (www.vialicensing.com) or through the respective patent owners individually for |
22 | | the purpose of encoding or decoding bit streams in products that are compliant |
23 | | with the ISO/IEC MPEG audio standards. Please note that most manufacturers of |
24 | | Android devices already license these patent claims through Via Licensing or |
25 | | directly from the patent owners, and therefore FDK AAC Codec software may |
26 | | already be covered under those patent licenses when it is used for those |
27 | | licensed purposes only. |
28 | | |
29 | | Commercially-licensed AAC software libraries, including floating-point versions |
30 | | with enhanced sound quality, are also available from Fraunhofer. Users are |
31 | | encouraged to check the Fraunhofer website for additional applications |
32 | | information and documentation. |
33 | | |
34 | | 2. COPYRIGHT LICENSE |
35 | | |
36 | | Redistribution and use in source and binary forms, with or without modification, |
37 | | are permitted without payment of copyright license fees provided that you |
38 | | satisfy the following conditions: |
39 | | |
40 | | You must retain the complete text of this software license in redistributions of |
41 | | the FDK AAC Codec or your modifications thereto in source code form. |
42 | | |
43 | | You must retain the complete text of this software license in the documentation |
44 | | and/or other materials provided with redistributions of the FDK AAC Codec or |
45 | | your modifications thereto in binary form. You must make available free of |
46 | | charge copies of the complete source code of the FDK AAC Codec and your |
47 | | modifications thereto to recipients of copies in binary form. |
48 | | |
49 | | The name of Fraunhofer may not be used to endorse or promote products derived |
50 | | from this library without prior written permission. |
51 | | |
52 | | You may not charge copyright license fees for anyone to use, copy or distribute |
53 | | the FDK AAC Codec software or your modifications thereto. |
54 | | |
55 | | Your modified versions of the FDK AAC Codec must carry prominent notices stating |
56 | | that you changed the software and the date of any change. For modified versions |
57 | | of the FDK AAC Codec, the term "Fraunhofer FDK AAC Codec Library for Android" |
58 | | must be replaced by the term "Third-Party Modified Version of the Fraunhofer FDK |
59 | | AAC Codec Library for Android." |
60 | | |
61 | | 3. NO PATENT LICENSE |
62 | | |
63 | | NO EXPRESS OR IMPLIED LICENSES TO ANY PATENT CLAIMS, including without |
64 | | limitation the patents of Fraunhofer, ARE GRANTED BY THIS SOFTWARE LICENSE. |
65 | | Fraunhofer provides no warranty of patent non-infringement with respect to this |
66 | | software. |
67 | | |
68 | | You may use this FDK AAC Codec software or modifications thereto only for |
69 | | purposes that are authorized by appropriate patent licenses. |
70 | | |
71 | | 4. DISCLAIMER |
72 | | |
73 | | This FDK AAC Codec software is provided by Fraunhofer on behalf of the copyright |
74 | | holders and contributors "AS IS" and WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, |
75 | | including but not limited to the implied warranties of merchantability and |
76 | | fitness for a particular purpose. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
77 | | CONTRIBUTORS BE LIABLE for any direct, indirect, incidental, special, exemplary, |
78 | | or consequential damages, including but not limited to procurement of substitute |
79 | | goods or services; loss of use, data, or profits, or business interruption, |
80 | | however caused and on any theory of liability, whether in contract, strict |
81 | | liability, or tort (including negligence), arising in any way out of the use of |
82 | | this software, even if advised of the possibility of such damage. |
83 | | |
84 | | 5. CONTACT INFORMATION |
85 | | |
86 | | Fraunhofer Institute for Integrated Circuits IIS |
87 | | Attention: Audio and Multimedia Departments - FDK AAC LL |
88 | | Am Wolfsmantel 33 |
89 | | 91058 Erlangen, Germany |
90 | | |
91 | | www.iis.fraunhofer.de/amm |
92 | | amm-info@iis.fraunhofer.de |
93 | | ----------------------------------------------------------------------------- */ |
94 | | |
95 | | /**************************** SBR encoder library ****************************** |
96 | | |
97 | | Author(s): |
98 | | |
99 | | Description: |
100 | | |
101 | | *******************************************************************************/ |
102 | | |
103 | | /*! |
104 | | \file |
105 | | \brief FDK resampler tool box:$Revision: 91655 $ |
106 | | \author M. Werner |
107 | | */ |
108 | | |
109 | | #include "resampler.h" |
110 | | |
111 | | #include "genericStds.h" |
112 | | |
113 | | /**************************************************************************/ |
114 | | /* BIQUAD Filter Specifications */ |
115 | | /**************************************************************************/ |
116 | | |
117 | 0 | #define B1 0 |
118 | 0 | #define B2 1 |
119 | 0 | #define A1 2 |
120 | 0 | #define A2 3 |
121 | | |
122 | | #define BQC(x) FL2FXCONST_SGL(x / 2) |
123 | | |
124 | | struct FILTER_PARAM { |
125 | | const FIXP_SGL *coeffa; /*! SOS matrix One row/section. Scaled using BQC(). |
126 | | Order of coefficients: B1,B2,A1,A2. B0=A0=1.0 */ |
127 | | FIXP_DBL g; /*! overall gain */ |
128 | | int Wc; /*! normalized passband bandwidth at input samplerate * 1000 */ |
129 | | int noCoeffs; /*! number of filter coeffs */ |
130 | | int delay; /*! delay in samples at input samplerate */ |
131 | | }; |
132 | | |
133 | 0 | #define BIQUAD_COEFSTEP 4 |
134 | | |
135 | | /** |
136 | | *\brief Low Pass |
137 | | Wc = 0,5, order 30, Stop Band -96dB. Wc criteria is "almost 0dB passband", not |
138 | | the usual -3db gain point. [b,a]=cheby2(30,96,0.505) [sos,g]=tf2sos(b,a) |
139 | | bandwidth 0.48 |
140 | | */ |
141 | | static const FIXP_SGL sos48[] = { |
142 | | BQC(1.98941075681938), BQC(0.999999996890811), |
143 | | BQC(0.863264527201963), BQC(0.189553799960663), |
144 | | BQC(1.90733804822445), BQC(1.00000001736189), |
145 | | BQC(0.836321575841691), BQC(0.203505809266564), |
146 | | BQC(1.75616665495325), BQC(0.999999946079721), |
147 | | BQC(0.784699225121588), BQC(0.230471265506986), |
148 | | BQC(1.55727745512726), BQC(1.00000011737815), |
149 | | BQC(0.712515423588351), BQC(0.268752723900498), |
150 | | BQC(1.33407591943643), BQC(0.999999795953228), |
151 | | BQC(0.625059117330989), BQC(0.316194685288965), |
152 | | BQC(1.10689898412458), BQC(1.00000035057114), |
153 | | BQC(0.52803514366398), BQC(0.370517843224669), |
154 | | BQC(0.89060371078454), BQC(0.999999343962822), |
155 | | BQC(0.426920462165257), BQC(0.429608200207746), |
156 | | BQC(0.694438261209433), BQC(1.0000008629792), |
157 | | BQC(0.326530699561716), BQC(0.491714450654174), |
158 | | BQC(0.523237800935322), BQC(1.00000101349782), |
159 | | BQC(0.230829556274851), BQC(0.555559034843281), |
160 | | BQC(0.378631165929563), BQC(0.99998986482665), |
161 | | BQC(0.142906422036095), BQC(0.620338874442411), |
162 | | BQC(0.260786911308437), BQC(1.00003261460178), |
163 | | BQC(0.0651008576256505), BQC(0.685759923926262), |
164 | | BQC(0.168409429188098), BQC(0.999933049695828), |
165 | | BQC(-0.000790067789975562), BQC(0.751905896602325), |
166 | | BQC(0.100724533818628), BQC(1.00009472669872), |
167 | | BQC(-0.0533772830257041), BQC(0.81930744384525), |
168 | | BQC(0.0561434357867363), BQC(0.999911636304276), |
169 | | BQC(-0.0913550299236405), BQC(0.88883625875915), |
170 | | BQC(0.0341680678662057), BQC(1.00003667508676), |
171 | | BQC(-0.113405185536697), BQC(0.961756638268446)}; |
172 | | |
173 | | static const FIXP_DBL g48 = |
174 | | FL2FXCONST_DBL(0.002712866530047) - (FIXP_DBL)0x8000; |
175 | | |
176 | | static const struct FILTER_PARAM param_set48 = { |
177 | | sos48, g48, 480, 15, 4 /* LF 2 */ |
178 | | }; |
179 | | |
180 | | /** |
181 | | *\brief Low Pass |
182 | | Wc = 0,5, order 24, Stop Band -96dB. Wc criteria is "almost 0dB passband", not |
183 | | the usual -3db gain point. [b,a]=cheby2(24,96,0.5) [sos,g]=tf2sos(b,a) |
184 | | bandwidth 0.45 |
185 | | */ |
186 | | static const FIXP_SGL sos45[] = { |
187 | | BQC(1.982962601444), BQC(1.00000000007504), BQC(0.646113303737836), |
188 | | BQC(0.10851149979981), BQC(1.85334094281111), BQC(0.999999999677192), |
189 | | BQC(0.612073220102006), BQC(0.130022141698044), BQC(1.62541051415425), |
190 | | BQC(1.00000000080398), BQC(0.547879702855959), BQC(0.171165825133192), |
191 | | BQC(1.34554656923247), BQC(0.9999999980169), BQC(0.460373914508491), |
192 | | BQC(0.228677463376354), BQC(1.05656568503116), BQC(1.00000000569363), |
193 | | BQC(0.357891894038287), BQC(0.298676843912185), BQC(0.787967587877312), |
194 | | BQC(0.999999984415017), BQC(0.248826893211877), BQC(0.377441803512978), |
195 | | BQC(0.555480971120497), BQC(1.00000003583307), BQC(0.140614263345315), |
196 | | BQC(0.461979302213679), BQC(0.364986207070964), BQC(0.999999932084303), |
197 | | BQC(0.0392669446074516), BQC(0.55033451180825), BQC(0.216827267631558), |
198 | | BQC(1.00000010534682), BQC(-0.0506232228865103), BQC(0.641691581560946), |
199 | | BQC(0.108951672277119), BQC(0.999999871167516), BQC(-0.125584840183225), |
200 | | BQC(0.736367748771803), BQC(0.0387988607229035), BQC(1.00000011205574), |
201 | | BQC(-0.182814849097974), BQC(0.835802108714964), BQC(0.0042866175809225), |
202 | | BQC(0.999999954830813), BQC(-0.21965740617151), BQC(0.942623047782363)}; |
203 | | |
204 | | static const FIXP_DBL g45 = |
205 | | FL2FXCONST_DBL(0.00242743980909524) - (FIXP_DBL)0x8000; |
206 | | |
207 | | static const struct FILTER_PARAM param_set45 = { |
208 | | sos45, g45, 450, 12, 4 /* LF 2 */ |
209 | | }; |
210 | | |
211 | | /* |
212 | | Created by Octave 2.1.73, Mon Oct 13 17:31:32 2008 CEST |
213 | | Wc = 0,5, order 16, Stop Band -96dB damping. |
214 | | [b,a]=cheby2(16,96,0.5) |
215 | | [sos,g]=tf2sos(b,a) |
216 | | bandwidth = 0.41 |
217 | | */ |
218 | | |
219 | | static const FIXP_SGL sos41[] = { |
220 | | BQC(1.96193625292), BQC(0.999999999999964), BQC(0.169266178786789), |
221 | | BQC(0.0128823300475907), BQC(1.68913437662092), BQC(1.00000000000053), |
222 | | BQC(0.124751503206552), BQC(0.0537472273950989), BQC(1.27274692366017), |
223 | | BQC(0.999999999995674), BQC(0.0433108625178357), BQC(0.131015753236317), |
224 | | BQC(0.85214175088395), BQC(1.00000000001813), BQC(-0.0625658152550408), |
225 | | BQC(0.237763778993806), BQC(0.503841579939009), BQC(0.999999999953223), |
226 | | BQC(-0.179176128722865), BQC(0.367475236424474), BQC(0.249990711986162), |
227 | | BQC(1.00000000007952), BQC(-0.294425165824676), BQC(0.516594857170212), |
228 | | BQC(0.087971668680286), BQC(0.999999999915528), BQC(-0.398956566777928), |
229 | | BQC(0.686417767801123), BQC(0.00965373325350294), BQC(1.00000000003744), |
230 | | BQC(-0.48579173764817), BQC(0.884931534239068)}; |
231 | | |
232 | | static const FIXP_DBL g41 = FL2FXCONST_DBL(0.00155956951169248); |
233 | | |
234 | | static const struct FILTER_PARAM param_set41 = { |
235 | | sos41, g41, 410, 8, 5 /* LF 3 */ |
236 | | }; |
237 | | |
238 | | /* |
239 | | # Created by Octave 2.1.73, Mon Oct 13 17:55:33 2008 CEST |
240 | | Wc = 0,5, order 12, Stop Band -96dB damping. |
241 | | [b,a]=cheby2(12,96,0.5); |
242 | | [sos,g]=tf2sos(b,a) |
243 | | */ |
244 | | static const FIXP_SGL sos35[] = { |
245 | | BQC(1.93299325235762), BQC(0.999999999999985), BQC(-0.140733187246596), |
246 | | BQC(0.0124139497836062), BQC(1.4890416764109), BQC(1.00000000000011), |
247 | | BQC(-0.198215402588504), BQC(0.0746730616584138), BQC(0.918450161309795), |
248 | | BQC(0.999999999999619), BQC(-0.30133912791941), BQC(0.192276468839529), |
249 | | BQC(0.454877024246818), BQC(1.00000000000086), BQC(-0.432337328809815), |
250 | | BQC(0.356852933642815), BQC(0.158017147118507), BQC(0.999999999998876), |
251 | | BQC(-0.574817494249777), BQC(0.566380436970833), BQC(0.0171834649478749), |
252 | | BQC(1.00000000000055), BQC(-0.718581178041165), BQC(0.83367484487889)}; |
253 | | |
254 | | static const FIXP_DBL g35 = FL2FXCONST_DBL(0.00162580994125131); |
255 | | |
256 | | static const struct FILTER_PARAM param_set35 = {sos35, g35, 350, 6, 4}; |
257 | | |
258 | | /* |
259 | | # Created by Octave 2.1.73, Mon Oct 13 18:15:38 2008 CEST |
260 | | Wc = 0,5, order 8, Stop Band -96dB damping. |
261 | | [b,a]=cheby2(8,96,0.5); |
262 | | [sos,g]=tf2sos(b,a) |
263 | | */ |
264 | | static const FIXP_SGL sos25[] = { |
265 | | BQC(1.85334094301225), BQC(1.0), |
266 | | BQC(-0.702127214212663), BQC(0.132452403998767), |
267 | | BQC(1.056565682167), BQC(0.999999999999997), |
268 | | BQC(-0.789503667880785), BQC(0.236328693569128), |
269 | | BQC(0.364986307455489), BQC(0.999999999999996), |
270 | | BQC(-0.955191189843375), BQC(0.442966457936379), |
271 | | BQC(0.0387985751642125), BQC(1.0), |
272 | | BQC(-1.19817786088084), BQC(0.770493895456328)}; |
273 | | |
274 | | static const FIXP_DBL g25 = FL2FXCONST_DBL(0.000945182835294559); |
275 | | |
276 | | static const struct FILTER_PARAM param_set25 = {sos25, g25, 250, 4, 5}; |
277 | | |
278 | | /* Must be sorted in descending order */ |
279 | | static const struct FILTER_PARAM *const filter_paramSet[] = { |
280 | | ¶m_set48, ¶m_set45, ¶m_set41, ¶m_set35, ¶m_set25}; |
281 | | |
282 | | /**************************************************************************/ |
283 | | /* Resampler Functions */ |
284 | | /**************************************************************************/ |
285 | | |
286 | | /*! |
287 | | \brief Reset downsampler instance and clear delay lines |
288 | | |
289 | | \return success of operation |
290 | | */ |
291 | | |
292 | | INT FDKaacEnc_InitDownsampler( |
293 | | DOWNSAMPLER *DownSampler, /*!< pointer to downsampler instance */ |
294 | | int Wc, /*!< normalized cutoff freq * 1000* */ |
295 | | int ratio) /*!< downsampler ratio */ |
296 | | |
297 | 0 | { |
298 | 0 | UINT i; |
299 | 0 | const struct FILTER_PARAM *currentSet = NULL; |
300 | |
|
301 | 0 | FDKmemclear(DownSampler->downFilter.states, |
302 | 0 | sizeof(DownSampler->downFilter.states)); |
303 | 0 | DownSampler->downFilter.ptr = 0; |
304 | | |
305 | | /* |
306 | | find applicable parameter set |
307 | | */ |
308 | 0 | currentSet = filter_paramSet[0]; |
309 | 0 | for (i = 1; i < sizeof(filter_paramSet) / sizeof(struct FILTER_PARAM *); |
310 | 0 | i++) { |
311 | 0 | if (filter_paramSet[i]->Wc <= Wc) { |
312 | 0 | break; |
313 | 0 | } |
314 | 0 | currentSet = filter_paramSet[i]; |
315 | 0 | } |
316 | |
|
317 | 0 | DownSampler->downFilter.coeffa = currentSet->coeffa; |
318 | |
|
319 | 0 | DownSampler->downFilter.gain = currentSet->g; |
320 | 0 | FDK_ASSERT(currentSet->noCoeffs <= MAXNR_SECTIONS * 2); |
321 | | |
322 | 0 | DownSampler->downFilter.noCoeffs = currentSet->noCoeffs; |
323 | 0 | DownSampler->delay = currentSet->delay; |
324 | 0 | DownSampler->downFilter.Wc = currentSet->Wc; |
325 | |
|
326 | 0 | DownSampler->ratio = ratio; |
327 | 0 | DownSampler->pending = ratio - 1; |
328 | 0 | return (1); |
329 | 0 | } |
330 | | |
331 | | /*! |
332 | | \brief faster simple folding operation |
333 | | Filter: |
334 | | H(z) = A(z)/B(z) |
335 | | with |
336 | | A(z) = a[0]*z^0 + a[1]*z^1 + a[2]*z^2 ... a[n]*z^n |
337 | | |
338 | | \return filtered value |
339 | | */ |
340 | | |
341 | | static inline INT_PCM AdvanceFilter( |
342 | | LP_FILTER *downFilter, /*!< pointer to iir filter instance */ |
343 | | INT_PCM *pInput, /*!< input of filter */ |
344 | 0 | int downRatio) { |
345 | 0 | INT_PCM output; |
346 | 0 | int i, n; |
347 | |
|
348 | 0 | #define BIQUAD_SCALE 12 |
349 | |
|
350 | 0 | FIXP_DBL y = FL2FXCONST_DBL(0.0f); |
351 | 0 | FIXP_DBL input; |
352 | |
|
353 | 0 | for (n = 0; n < downRatio; n++) { |
354 | 0 | FIXP_BQS(*states)[2] = downFilter->states; |
355 | 0 | const FIXP_SGL *coeff = downFilter->coeffa; |
356 | 0 | int s1, s2; |
357 | |
|
358 | 0 | s1 = downFilter->ptr; |
359 | 0 | s2 = s1 ^ 1; |
360 | |
|
361 | 0 | #if (SAMPLE_BITS == 16) |
362 | 0 | input = ((FIXP_DBL)pInput[n]) << (DFRACT_BITS - SAMPLE_BITS - BIQUAD_SCALE); |
363 | | #elif (SAMPLE_BITS == 32) |
364 | | input = pInput[n] >> BIQUAD_SCALE; |
365 | | #else |
366 | | #error NOT IMPLEMENTED |
367 | | #endif |
368 | |
|
369 | 0 | FIXP_BQS state1, state2, state1b, state2b; |
370 | |
|
371 | 0 | state1 = states[0][s1]; |
372 | 0 | state2 = states[0][s2]; |
373 | | |
374 | | /* Loop over sections */ |
375 | 0 | for (i = 0; i < downFilter->noCoeffs; i++) { |
376 | 0 | FIXP_DBL state0; |
377 | | |
378 | | /* Load merged states (from next section) */ |
379 | 0 | state1b = states[i + 1][s1]; |
380 | 0 | state2b = states[i + 1][s2]; |
381 | |
|
382 | 0 | state0 = input + fMult(state1, coeff[B1]) + fMult(state2, coeff[B2]); |
383 | 0 | y = state0 - fMult(state1b, coeff[A1]) - fMult(state2b, coeff[A2]); |
384 | | |
385 | | /* Store new feed forward merge state */ |
386 | 0 | states[i + 1][s2] = y << 1; |
387 | | /* Store new feed backward state */ |
388 | 0 | states[i][s2] = input << 1; |
389 | | |
390 | | /* Feedback output to next section. */ |
391 | 0 | input = y; |
392 | | |
393 | | /* Transfer merged states */ |
394 | 0 | state1 = state1b; |
395 | 0 | state2 = state2b; |
396 | | |
397 | | /* Step to next coef set */ |
398 | 0 | coeff += BIQUAD_COEFSTEP; |
399 | 0 | } |
400 | 0 | downFilter->ptr ^= 1; |
401 | 0 | } |
402 | | /* Apply global gain */ |
403 | 0 | y = fMult(y, downFilter->gain); |
404 | | |
405 | | /* Apply final gain/scaling to output */ |
406 | 0 | #if (SAMPLE_BITS == 16) |
407 | 0 | output = (INT_PCM)SATURATE_RIGHT_SHIFT( |
408 | 0 | y + (FIXP_DBL)(1 << (DFRACT_BITS - SAMPLE_BITS - BIQUAD_SCALE - 1)), |
409 | 0 | DFRACT_BITS - SAMPLE_BITS - BIQUAD_SCALE, SAMPLE_BITS); |
410 | | // output = (INT_PCM) SATURATE_RIGHT_SHIFT(y, |
411 | | // DFRACT_BITS-SAMPLE_BITS-BIQUAD_SCALE, SAMPLE_BITS); |
412 | | #else |
413 | | output = SATURATE_LEFT_SHIFT(y, BIQUAD_SCALE, SAMPLE_BITS); |
414 | | #endif |
415 | |
|
416 | 0 | return output; |
417 | 0 | } |
418 | | |
419 | | /*! |
420 | | \brief FDKaacEnc_Downsample numInSamples of type INT_PCM |
421 | | Returns number of output samples in numOutSamples |
422 | | |
423 | | \return success of operation |
424 | | */ |
425 | | |
426 | | INT FDKaacEnc_Downsample( |
427 | | DOWNSAMPLER *DownSampler, /*!< pointer to downsampler instance */ |
428 | | INT_PCM *inSamples, /*!< pointer to input samples */ |
429 | | INT numInSamples, /*!< number of input samples */ |
430 | | INT_PCM *outSamples, /*!< pointer to output samples */ |
431 | | INT *numOutSamples /*!< pointer tp number of output samples */ |
432 | 0 | ) { |
433 | 0 | INT i; |
434 | 0 | *numOutSamples = 0; |
435 | |
|
436 | 0 | for (i = 0; i < numInSamples; i += DownSampler->ratio) { |
437 | 0 | *outSamples = AdvanceFilter(&(DownSampler->downFilter), &inSamples[i], |
438 | 0 | DownSampler->ratio); |
439 | 0 | outSamples++; |
440 | 0 | } |
441 | 0 | *numOutSamples = numInSamples / DownSampler->ratio; |
442 | |
|
443 | 0 | return 0; |
444 | 0 | } |