4 #include "bformatdec.h"
6 #include "mixer_defs.h"
14 void bandsplit_init(BandSplitter
*splitter
, ALfloat freq_mult
)
16 ALfloat w
= freq_mult
* F_TAU
;
19 splitter
->coeff
= (sinf(w
) - 1.0f
) / cw
;
21 splitter
->coeff
= cw
* -0.5f
;
23 splitter
->lp_z1
= 0.0f
;
24 splitter
->lp_z2
= 0.0f
;
25 splitter
->hp_z1
= 0.0f
;
28 void bandsplit_clear(BandSplitter
*splitter
)
30 splitter
->lp_z1
= 0.0f
;
31 splitter
->lp_z2
= 0.0f
;
32 splitter
->hp_z1
= 0.0f
;
35 void bandsplit_process(BandSplitter
*splitter
, ALfloat
*restrict hpout
, ALfloat
*restrict lpout
,
36 const ALfloat
*input
, ALsizei count
)
42 coeff
= splitter
->coeff
*0.5f
+ 0.5f
;
45 for(i
= 0;i
< count
;i
++)
62 coeff
= splitter
->coeff
;
64 for(i
= 0;i
< count
;i
++)
72 hpout
[i
] = x
- lpout
[i
];
78 void splitterap_init(SplitterAllpass
*splitter
, ALfloat freq_mult
)
80 ALfloat w
= freq_mult
* F_TAU
;
83 splitter
->coeff
= (sinf(w
) - 1.0f
) / cw
;
85 splitter
->coeff
= cw
* -0.5f
;
90 void splitterap_clear(SplitterAllpass
*splitter
)
95 void splitterap_process(SplitterAllpass
*splitter
, ALfloat
*restrict samples
, ALsizei count
)
101 coeff
= splitter
->coeff
;
103 for(i
= 0;i
< count
;i
++)
117 static const ALfloat UnitScale
[MAX_AMBI_COEFFS
] = {
118 1.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
,
119 1.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
121 static const ALfloat SN3D2N3DScale
[MAX_AMBI_COEFFS
] = {
122 1.000000000f
, /* ACN 0 (W), sqrt(1) */
123 1.732050808f
, /* ACN 1 (Y), sqrt(3) */
124 1.732050808f
, /* ACN 2 (Z), sqrt(3) */
125 1.732050808f
, /* ACN 3 (X), sqrt(3) */
126 2.236067978f
, /* ACN 4 (V), sqrt(5) */
127 2.236067978f
, /* ACN 5 (T), sqrt(5) */
128 2.236067978f
, /* ACN 6 (R), sqrt(5) */
129 2.236067978f
, /* ACN 7 (S), sqrt(5) */
130 2.236067978f
, /* ACN 8 (U), sqrt(5) */
131 2.645751311f
, /* ACN 9 (Q), sqrt(7) */
132 2.645751311f
, /* ACN 10 (O), sqrt(7) */
133 2.645751311f
, /* ACN 11 (M), sqrt(7) */
134 2.645751311f
, /* ACN 12 (K), sqrt(7) */
135 2.645751311f
, /* ACN 13 (L), sqrt(7) */
136 2.645751311f
, /* ACN 14 (N), sqrt(7) */
137 2.645751311f
, /* ACN 15 (P), sqrt(7) */
139 static const ALfloat FuMa2N3DScale
[MAX_AMBI_COEFFS
] = {
140 1.414213562f
, /* ACN 0 (W), sqrt(2) */
141 1.732050808f
, /* ACN 1 (Y), sqrt(3) */
142 1.732050808f
, /* ACN 2 (Z), sqrt(3) */
143 1.732050808f
, /* ACN 3 (X), sqrt(3) */
144 1.936491673f
, /* ACN 4 (V), sqrt(15)/2 */
145 1.936491673f
, /* ACN 5 (T), sqrt(15)/2 */
146 2.236067978f
, /* ACN 6 (R), sqrt(5) */
147 1.936491673f
, /* ACN 7 (S), sqrt(15)/2 */
148 1.936491673f
, /* ACN 8 (U), sqrt(15)/2 */
149 2.091650066f
, /* ACN 9 (Q), sqrt(35/8) */
150 1.972026594f
, /* ACN 10 (O), sqrt(35)/3 */
151 2.231093404f
, /* ACN 11 (M), sqrt(224/45) */
152 2.645751311f
, /* ACN 12 (K), sqrt(7) */
153 2.231093404f
, /* ACN 13 (L), sqrt(224/45) */
154 1.972026594f
, /* ACN 14 (N), sqrt(35)/3 */
155 2.091650066f
, /* ACN 15 (P), sqrt(35/8) */
165 /* These points are in AL coordinates! */
166 static const ALfloat Ambi3DPoints
[8][3] = {
167 { -0.577350269f
, 0.577350269f
, -0.577350269f
},
168 { 0.577350269f
, 0.577350269f
, -0.577350269f
},
169 { -0.577350269f
, 0.577350269f
, 0.577350269f
},
170 { 0.577350269f
, 0.577350269f
, 0.577350269f
},
171 { -0.577350269f
, -0.577350269f
, -0.577350269f
},
172 { 0.577350269f
, -0.577350269f
, -0.577350269f
},
173 { -0.577350269f
, -0.577350269f
, 0.577350269f
},
174 { 0.577350269f
, -0.577350269f
, 0.577350269f
},
176 static const ALfloat Ambi3DDecoder
[8][FB_Max
][MAX_AMBI_COEFFS
] = {
177 { { 0.25f
, 0.1443375672f
, 0.1443375672f
, 0.1443375672f
}, { 0.125f
, 0.125f
, 0.125f
, 0.125f
} },
178 { { 0.25f
, -0.1443375672f
, 0.1443375672f
, 0.1443375672f
}, { 0.125f
, -0.125f
, 0.125f
, 0.125f
} },
179 { { 0.25f
, 0.1443375672f
, 0.1443375672f
, -0.1443375672f
}, { 0.125f
, 0.125f
, 0.125f
, -0.125f
} },
180 { { 0.25f
, -0.1443375672f
, 0.1443375672f
, -0.1443375672f
}, { 0.125f
, -0.125f
, 0.125f
, -0.125f
} },
181 { { 0.25f
, 0.1443375672f
, -0.1443375672f
, 0.1443375672f
}, { 0.125f
, 0.125f
, -0.125f
, 0.125f
} },
182 { { 0.25f
, -0.1443375672f
, -0.1443375672f
, 0.1443375672f
}, { 0.125f
, -0.125f
, -0.125f
, 0.125f
} },
183 { { 0.25f
, 0.1443375672f
, -0.1443375672f
, -0.1443375672f
}, { 0.125f
, 0.125f
, -0.125f
, -0.125f
} },
184 { { 0.25f
, -0.1443375672f
, -0.1443375672f
, -0.1443375672f
}, { 0.125f
, -0.125f
, -0.125f
, -0.125f
} },
188 static RowMixerFunc MixMatrixRow
= MixRow_C
;
191 static alonce_flag bformatdec_inited
= AL_ONCE_FLAG_INIT
;
193 static void init_bformatdec(void)
195 MixMatrixRow
= SelectRowMixer();
199 /* NOTE: BandSplitter filters are unused with single-band decoding */
200 typedef struct BFormatDec
{
201 ALboolean Enabled
[MAX_OUTPUT_CHANNELS
];
204 alignas(16) ALfloat Dual
[MAX_OUTPUT_CHANNELS
][FB_Max
][MAX_AMBI_COEFFS
];
205 alignas(16) ALfloat Single
[MAX_OUTPUT_CHANNELS
][MAX_AMBI_COEFFS
];
208 BandSplitter XOver
[MAX_AMBI_COEFFS
];
210 ALfloat (*Samples
)[BUFFERSIZE
];
211 /* These two alias into Samples */
212 ALfloat (*SamplesHF
)[BUFFERSIZE
];
213 ALfloat (*SamplesLF
)[BUFFERSIZE
];
215 alignas(16) ALfloat ChannelMix
[BUFFERSIZE
];
219 ALfloat Gains
[FB_Max
];
226 BFormatDec
*bformatdec_alloc()
228 alcall_once(&bformatdec_inited
, init_bformatdec
);
229 return al_calloc(16, sizeof(BFormatDec
));
232 void bformatdec_free(BFormatDec
*dec
)
236 al_free(dec
->Samples
);
238 dec
->SamplesHF
= NULL
;
239 dec
->SamplesLF
= NULL
;
241 memset(dec
, 0, sizeof(*dec
));
246 void bformatdec_reset(BFormatDec
*dec
, const AmbDecConf
*conf
, ALsizei chancount
, ALuint srate
, const ALsizei chanmap
[MAX_OUTPUT_CHANNELS
])
248 static const ALsizei map2DTo3D
[MAX_AMBI2D_COEFFS
] = {
251 const ALfloat
*coeff_scale
= UnitScale
;
256 al_free(dec
->Samples
);
258 dec
->SamplesHF
= NULL
;
259 dec
->SamplesLF
= NULL
;
261 dec
->NumChannels
= chancount
;
262 dec
->Samples
= al_calloc(16, dec
->NumChannels
*2 * sizeof(dec
->Samples
[0]));
263 dec
->SamplesHF
= dec
->Samples
;
264 dec
->SamplesLF
= dec
->SamplesHF
+ dec
->NumChannels
;
266 for(i
= 0;i
< MAX_OUTPUT_CHANNELS
;i
++)
267 dec
->Enabled
[i
] = AL_FALSE
;
268 for(i
= 0;i
< conf
->NumSpeakers
;i
++)
269 dec
->Enabled
[chanmap
[i
]] = AL_TRUE
;
271 if(conf
->CoeffScale
== ADS_SN3D
)
272 coeff_scale
= SN3D2N3DScale
;
273 else if(conf
->CoeffScale
== ADS_FuMa
)
274 coeff_scale
= FuMa2N3DScale
;
276 memset(dec
->UpSampler
, 0, sizeof(dec
->UpSampler
));
277 ratio
= 400.0f
/ (ALfloat
)srate
;
279 bandsplit_init(&dec
->UpSampler
[i
].XOver
, ratio
);
280 if((conf
->ChanMask
&AMBI_PERIPHONIC_MASK
))
284 dec
->UpSampler
[0].Gains
[FB_HighFreq
] = (dec
->NumChannels
> 9) ? W_SCALE3D_THIRD
:
285 (dec
->NumChannels
> 4) ? W_SCALE3D_SECOND
: 1.0f
;
286 dec
->UpSampler
[0].Gains
[FB_LowFreq
] = 1.0f
;
289 dec
->UpSampler
[i
].Gains
[FB_HighFreq
] = (dec
->NumChannels
> 9) ? XYZ_SCALE3D_THIRD
:
290 (dec
->NumChannels
> 4) ? XYZ_SCALE3D_SECOND
: 1.0f
;
291 dec
->UpSampler
[i
].Gains
[FB_LowFreq
] = 1.0f
;
298 dec
->UpSampler
[0].Gains
[FB_HighFreq
] = (dec
->NumChannels
> 5) ? W_SCALE2D_THIRD
:
299 (dec
->NumChannels
> 3) ? W_SCALE2D_SECOND
: 1.0f
;
300 dec
->UpSampler
[0].Gains
[FB_LowFreq
] = 1.0f
;
303 dec
->UpSampler
[i
].Gains
[FB_HighFreq
] = (dec
->NumChannels
> 5) ? XYZ_SCALE2D_THIRD
:
304 (dec
->NumChannels
> 3) ? XYZ_SCALE2D_SECOND
: 1.0f
;
305 dec
->UpSampler
[i
].Gains
[FB_LowFreq
] = 1.0f
;
307 dec
->UpSampler
[3].Gains
[FB_HighFreq
] = 0.0f
;
308 dec
->UpSampler
[3].Gains
[FB_LowFreq
] = 0.0f
;
311 memset(&dec
->Matrix
, 0, sizeof(dec
->Matrix
));
312 if(conf
->FreqBands
== 1)
314 dec
->DualBand
= AL_FALSE
;
315 for(i
= 0;i
< conf
->NumSpeakers
;i
++)
317 ALsizei chan
= chanmap
[i
];
323 for(j
= 0,k
= 0;j
< MAX_AMBI2D_COEFFS
;j
++)
325 ALsizei l
= map2DTo3D
[j
];
326 if(j
== 0) gain
= conf
->HFOrderGain
[0];
327 else if(j
== 1) gain
= conf
->HFOrderGain
[1];
328 else if(j
== 3) gain
= conf
->HFOrderGain
[2];
329 else if(j
== 5) gain
= conf
->HFOrderGain
[3];
330 if((conf
->ChanMask
&(1<<l
)))
331 dec
->Matrix
.Single
[chan
][j
] = conf
->HFMatrix
[i
][k
++] / coeff_scale
[l
] *
337 for(j
= 0,k
= 0;j
< MAX_AMBI_COEFFS
;j
++)
339 if(j
== 0) gain
= conf
->HFOrderGain
[0];
340 else if(j
== 1) gain
= conf
->HFOrderGain
[1];
341 else if(j
== 4) gain
= conf
->HFOrderGain
[2];
342 else if(j
== 9) gain
= conf
->HFOrderGain
[3];
343 if((conf
->ChanMask
&(1<<j
)))
344 dec
->Matrix
.Single
[chan
][j
] = conf
->HFMatrix
[i
][k
++] / coeff_scale
[j
] *
352 dec
->DualBand
= AL_TRUE
;
354 ratio
= conf
->XOverFreq
/ (ALfloat
)srate
;
355 for(i
= 0;i
< MAX_AMBI_COEFFS
;i
++)
356 bandsplit_init(&dec
->XOver
[i
], ratio
);
358 ratio
= powf(10.0f
, conf
->XOverRatio
/ 40.0f
);
359 for(i
= 0;i
< conf
->NumSpeakers
;i
++)
361 ALsizei chan
= chanmap
[i
];
367 for(j
= 0,k
= 0;j
< MAX_AMBI2D_COEFFS
;j
++)
369 ALsizei l
= map2DTo3D
[j
];
370 if(j
== 0) gain
= conf
->HFOrderGain
[0] * ratio
;
371 else if(j
== 1) gain
= conf
->HFOrderGain
[1] * ratio
;
372 else if(j
== 3) gain
= conf
->HFOrderGain
[2] * ratio
;
373 else if(j
== 5) gain
= conf
->HFOrderGain
[3] * ratio
;
374 if((conf
->ChanMask
&(1<<l
)))
375 dec
->Matrix
.Dual
[chan
][FB_HighFreq
][j
] = conf
->HFMatrix
[i
][k
++] /
376 coeff_scale
[l
] * gain
;
378 for(j
= 0,k
= 0;j
< MAX_AMBI2D_COEFFS
;j
++)
380 ALsizei l
= map2DTo3D
[j
];
381 if(j
== 0) gain
= conf
->LFOrderGain
[0] / ratio
;
382 else if(j
== 1) gain
= conf
->LFOrderGain
[1] / ratio
;
383 else if(j
== 3) gain
= conf
->LFOrderGain
[2] / ratio
;
384 else if(j
== 5) gain
= conf
->LFOrderGain
[3] / ratio
;
385 if((conf
->ChanMask
&(1<<l
)))
386 dec
->Matrix
.Dual
[chan
][FB_LowFreq
][j
] = conf
->LFMatrix
[i
][k
++] /
387 coeff_scale
[l
] * gain
;
392 for(j
= 0,k
= 0;j
< MAX_AMBI_COEFFS
;j
++)
394 if(j
== 0) gain
= conf
->HFOrderGain
[0] * ratio
;
395 else if(j
== 1) gain
= conf
->HFOrderGain
[1] * ratio
;
396 else if(j
== 4) gain
= conf
->HFOrderGain
[2] * ratio
;
397 else if(j
== 9) gain
= conf
->HFOrderGain
[3] * ratio
;
398 if((conf
->ChanMask
&(1<<j
)))
399 dec
->Matrix
.Dual
[chan
][FB_HighFreq
][j
] = conf
->HFMatrix
[i
][k
++] /
400 coeff_scale
[j
] * gain
;
402 for(j
= 0,k
= 0;j
< MAX_AMBI_COEFFS
;j
++)
404 if(j
== 0) gain
= conf
->LFOrderGain
[0] / ratio
;
405 else if(j
== 1) gain
= conf
->LFOrderGain
[1] / ratio
;
406 else if(j
== 4) gain
= conf
->LFOrderGain
[2] / ratio
;
407 else if(j
== 9) gain
= conf
->LFOrderGain
[3] / ratio
;
408 if((conf
->ChanMask
&(1<<j
)))
409 dec
->Matrix
.Dual
[chan
][FB_LowFreq
][j
] = conf
->LFMatrix
[i
][k
++] /
410 coeff_scale
[j
] * gain
;
418 void bformatdec_process(struct BFormatDec
*dec
, ALfloat (*restrict OutBuffer
)[BUFFERSIZE
], ALsizei OutChannels
, const ALfloat (*restrict InSamples
)[BUFFERSIZE
], ALsizei SamplesToDo
)
422 OutBuffer
= ASSUME_ALIGNED(OutBuffer
, 16);
425 for(i
= 0;i
< dec
->NumChannels
;i
++)
426 bandsplit_process(&dec
->XOver
[i
], dec
->SamplesHF
[i
], dec
->SamplesLF
[i
],
427 InSamples
[i
], SamplesToDo
);
429 for(chan
= 0;chan
< OutChannels
;chan
++)
431 if(!dec
->Enabled
[chan
])
434 memset(dec
->ChannelMix
, 0, SamplesToDo
*sizeof(ALfloat
));
435 MixMatrixRow(dec
->ChannelMix
, dec
->Matrix
.Dual
[chan
][FB_HighFreq
],
436 SAFE_CONST(ALfloatBUFFERSIZE
*,dec
->SamplesHF
), dec
->NumChannels
, 0,
439 MixMatrixRow(dec
->ChannelMix
, dec
->Matrix
.Dual
[chan
][FB_LowFreq
],
440 SAFE_CONST(ALfloatBUFFERSIZE
*,dec
->SamplesLF
), dec
->NumChannels
, 0,
444 for(i
= 0;i
< SamplesToDo
;i
++)
445 OutBuffer
[chan
][i
] += dec
->ChannelMix
[i
];
450 for(chan
= 0;chan
< OutChannels
;chan
++)
452 if(!dec
->Enabled
[chan
])
455 memset(dec
->ChannelMix
, 0, SamplesToDo
*sizeof(ALfloat
));
456 MixMatrixRow(dec
->ChannelMix
, dec
->Matrix
.Single
[chan
], InSamples
,
457 dec
->NumChannels
, 0, SamplesToDo
);
459 for(i
= 0;i
< SamplesToDo
;i
++)
460 OutBuffer
[chan
][i
] += dec
->ChannelMix
[i
];
466 void bformatdec_upSample(struct BFormatDec
*dec
, ALfloat (*restrict OutBuffer
)[BUFFERSIZE
], const ALfloat (*restrict InSamples
)[BUFFERSIZE
], ALsizei InChannels
, ALsizei SamplesToDo
)
470 /* This up-sampler leverages the differences observed in dual-band second-
471 * and third-order decoder matrices compared to first-order. For the same
472 * output channel configuration, the low-frequency matrix has identical
473 * coefficients in the shared input channels, while the high-frequency
474 * matrix has extra scalars applied to the W channel and X/Y/Z channels.
475 * Mixing the first-order content into the higher-order stream with the
476 * appropriate counter-scales applied to the HF response results in the
477 * subsequent higher-order decode generating the same response as a first-
480 for(i
= 0;i
< InChannels
;i
++)
482 /* First, split the first-order components into low and high frequency
485 bandsplit_process(&dec
->UpSampler
[i
].XOver
,
486 dec
->Samples
[FB_HighFreq
], dec
->Samples
[FB_LowFreq
],
487 InSamples
[i
], SamplesToDo
490 /* Now write each band to the output. */
491 MixMatrixRow(OutBuffer
[i
], dec
->UpSampler
[i
].Gains
,
492 SAFE_CONST(ALfloatBUFFERSIZE
*,dec
->Samples
), FB_Max
, 0,
499 #define INVALID_UPSAMPLE_INDEX INT_MAX
501 static ALsizei
GetACNIndex(const BFChannelConfig
*chans
, ALsizei numchans
, ALsizei acn
)
504 for(i
= 0;i
< numchans
;i
++)
506 if(chans
[i
].Index
== acn
)
509 return INVALID_UPSAMPLE_INDEX
;
511 #define GetChannelForACN(b, a) GetACNIndex((b).Ambi.Map, (b).NumChannels, (a))
513 typedef struct AmbiUpsampler
{
514 alignas(16) ALfloat Samples
[FB_Max
][BUFFERSIZE
];
516 BandSplitter XOver
[4];
518 ALfloat Gains
[4][MAX_OUTPUT_CHANNELS
][FB_Max
];
521 AmbiUpsampler
*ambiup_alloc()
523 alcall_once(&bformatdec_inited
, init_bformatdec
);
524 return al_calloc(16, sizeof(AmbiUpsampler
));
527 void ambiup_free(struct AmbiUpsampler
*ambiup
)
532 void ambiup_reset(struct AmbiUpsampler
*ambiup
, const ALCdevice
*device
)
537 ratio
= 400.0f
/ (ALfloat
)device
->Frequency
;
539 bandsplit_init(&ambiup
->XOver
[i
], ratio
);
541 memset(ambiup
->Gains
, 0, sizeof(ambiup
->Gains
));
542 if(device
->Dry
.CoeffCount
> 0)
544 ALfloat encgains
[8][MAX_OUTPUT_CHANNELS
];
548 for(i
= 0;i
< COUNTOF(Ambi3DPoints
);i
++)
550 ALfloat coeffs
[MAX_AMBI_COEFFS
] = { 0.0f
};
551 CalcDirectionCoeffs(Ambi3DPoints
[i
], 0.0f
, coeffs
);
552 ComputePanningGains(device
->Dry
, coeffs
, 1.0f
, encgains
[i
]);
555 /* Combine the matrices that do the in->virt and virt->out conversions
556 * so we get a single in->out conversion. NOTE: the Encoder matrix
557 * (encgains) and output are transposed, so the input channels line up
558 * with the rows and the output channels line up with the columns.
562 for(j
= 0;j
< device
->Dry
.NumChannels
;j
++)
564 ALfloat hfgain
=0.0f
, lfgain
=0.0f
;
565 for(k
= 0;k
< COUNTOF(Ambi3DDecoder
);k
++)
567 hfgain
+= Ambi3DDecoder
[k
][FB_HighFreq
][i
]*encgains
[k
][j
];
568 lfgain
+= Ambi3DDecoder
[k
][FB_LowFreq
][i
]*encgains
[k
][j
];
570 ambiup
->Gains
[i
][j
][FB_HighFreq
] = hfgain
;
571 ambiup
->Gains
[i
][j
][FB_LowFreq
] = lfgain
;
577 /* Assumes full 3D/periphonic on the input and output mixes! */
578 ALfloat w_scale
= (device
->Dry
.NumChannels
> 9) ? W_SCALE3D_THIRD
:
579 (device
->Dry
.NumChannels
> 4) ? W_SCALE3D_SECOND
: 1.0f
;
580 ALfloat xyz_scale
= (device
->Dry
.NumChannels
> 9) ? XYZ_SCALE3D_THIRD
:
581 (device
->Dry
.NumChannels
> 4) ? XYZ_SCALE3D_SECOND
: 1.0f
;
584 ALsizei index
= GetChannelForACN(device
->Dry
, i
);
585 if(index
!= INVALID_UPSAMPLE_INDEX
)
587 ALfloat scale
= device
->Dry
.Ambi
.Map
[index
].Scale
;
588 ambiup
->Gains
[i
][index
][FB_HighFreq
] = scale
* ((i
==0) ? w_scale
: xyz_scale
);
589 ambiup
->Gains
[i
][index
][FB_LowFreq
] = scale
;
595 void ambiup_process(struct AmbiUpsampler
*ambiup
, ALfloat (*restrict OutBuffer
)[BUFFERSIZE
], ALsizei OutChannels
, const ALfloat (*restrict InSamples
)[BUFFERSIZE
], ALsizei SamplesToDo
)
601 bandsplit_process(&ambiup
->XOver
[i
],
602 ambiup
->Samples
[FB_HighFreq
], ambiup
->Samples
[FB_LowFreq
],
603 InSamples
[i
], SamplesToDo
606 for(j
= 0;j
< OutChannels
;j
++)
607 MixMatrixRow(OutBuffer
[j
], ambiup
->Gains
[i
][j
],
608 SAFE_CONST(ALfloatBUFFERSIZE
*,ambiup
->Samples
), FB_Max
, 0,