]> git.sesse.net Git - vlc/blob - modules/audio_filter/equalizer.c
Merge branch 'master' into lpcm_encoder
[vlc] / modules / audio_filter / equalizer.c
1 /*****************************************************************************
2  * equalizer.c:
3  *****************************************************************************
4  * Copyright (C) 2004-2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <math.h>
33
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_charset.h>
37
38 #include <vlc_aout.h>
39 #include <vlc_filter.h>
40
41 #include "equalizer_presets.h"
42 /* TODO:
43  *  - add tables for other rates ( 22500, 11250, ...)
44  *  - optimize a bit (you can hardly do slower ;)
45  *  - add tables for more bands (15 and 32 would be cool), maybe with auto coeffs
46  *  computation (not too hard once the Q is found).
47  *  - support for external preset
48  *  - callback to handle preset changes on the fly
49  *  - ...
50  */
51
52 /*****************************************************************************
53  * Module descriptor
54  *****************************************************************************/
55 static int  Open ( vlc_object_t * );
56 static void Close( vlc_object_t * );
57
58 #define PRESET_TEXT N_( "Equalizer preset" )
59 #define PRESET_LONGTEXT N_("Preset to use for the equalizer." )
60
61 #define BANDS_TEXT N_( "Bands gain")
62 #define BANDS_LONGTEXT N_( \
63          "Don't use presets, but manually specified bands. You need to " \
64          "provide 10 values between -20dB and 20dB, separated by spaces, " \
65          "e.g. \"0 2 4 2 0 -2 -4 -2 0 2\"." )
66
67 #define TWOPASS_TEXT N_( "Two pass" )
68 #define TWOPASS_LONGTEXT N_( "Filter the audio twice. This provides a more "  \
69          "intense effect.")
70
71 #define PREAMP_TEXT N_("Global gain" )
72 #define PREAMP_LONGTEXT N_("Set the global gain in dB (-20 ... 20)." )
73
74 vlc_module_begin ()
75     set_description( N_("Equalizer with 10 bands") )
76     set_shortname( N_("Equalizer" ) )
77     set_capability( "audio filter", 0 )
78     set_category( CAT_AUDIO )
79     set_subcategory( SUBCAT_AUDIO_AFILTER )
80
81     add_string( "equalizer-preset", "flat", NULL, PRESET_TEXT,
82                 PRESET_LONGTEXT, false )
83         change_string_list( preset_list, preset_list_text, 0 )
84     add_string( "equalizer-bands", NULL, NULL, BANDS_TEXT,
85                 BANDS_LONGTEXT, true )
86     add_bool( "equalizer-2pass", false, NULL, TWOPASS_TEXT,
87               TWOPASS_LONGTEXT, true )
88     add_float( "equalizer-preamp", 12.0, NULL, PREAMP_TEXT,
89                PREAMP_LONGTEXT, true )
90     set_callbacks( Open, Close )
91     add_shortcut( "equalizer" )
92 vlc_module_end ()
93
94 /*****************************************************************************
95  * Local prototypes
96  *****************************************************************************/
97 struct filter_sys_t
98 {
99     /* Filter static config */
100     int i_band;
101     float *f_alpha;
102     float *f_beta;
103     float *f_gamma;
104
105     float f_newpreamp;
106     char *psz_newbands;
107     bool b_first;
108
109     /* Filter dyn config */
110     float *f_amp;   /* Per band amp */
111     float f_gamp;   /* Global preamp */
112     bool b_2eqz;
113
114     /* Filter state */
115     float x[32][2];
116     float y[32][128][2];
117
118     /* Second filter state */
119     float x2[32][2];
120     float y2[32][128][2];
121
122     vlc_mutex_t lock;
123 };
124
125 static block_t *DoWork( filter_t *, block_t * );
126
127 #define EQZ_IN_FACTOR (0.25)
128 static int  EqzInit( filter_t *, int );
129 static void EqzFilter( filter_t *, float *, float *, int, int );
130 static void EqzClean( filter_t * );
131
132 static int PresetCallback ( vlc_object_t *, char const *, vlc_value_t,
133                             vlc_value_t, void * );
134 static int PreampCallback ( vlc_object_t *, char const *, vlc_value_t,
135                             vlc_value_t, void * );
136 static int BandsCallback  ( vlc_object_t *, char const *, vlc_value_t,
137                             vlc_value_t, void * );
138 static int TwoPassCallback( vlc_object_t *, char const *, vlc_value_t,
139                             vlc_value_t, void * );
140
141
142
143 /*****************************************************************************
144  * Open:
145  *****************************************************************************/
146 static int Open( vlc_object_t *p_this )
147 {
148     filter_t     *p_filter = (filter_t *)p_this;
149     filter_sys_t *p_sys;
150
151     if( p_filter->fmt_in.audio.i_format != VLC_CODEC_FL32 ||
152         p_filter->fmt_out.audio.i_format != VLC_CODEC_FL32 )
153     {
154         p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
155         p_filter->fmt_out.audio.i_format = VLC_CODEC_FL32;
156         msg_Warn( p_filter, "bad input or output format" );
157         return VLC_EGENERIC;
158     }
159     if ( !AOUT_FMTS_SIMILAR( &p_filter->fmt_in.audio, &p_filter->fmt_out.audio ) )
160     {
161         memcpy( &p_filter->fmt_out.audio, &p_filter->fmt_in.audio,
162                 sizeof(audio_sample_format_t) );
163         msg_Warn( p_filter, "input and output formats are not similar" );
164         return VLC_EGENERIC;
165     }
166
167     p_filter->pf_audio_filter = DoWork;
168
169     /* Allocate structure */
170     p_sys = p_filter->p_sys = malloc( sizeof( *p_sys ) );
171     if( !p_sys )
172         return VLC_ENOMEM;
173
174     vlc_mutex_init( &p_sys->lock );
175     if( EqzInit( p_filter, p_filter->fmt_in.audio.i_rate ) != VLC_SUCCESS )
176     {
177         vlc_mutex_destroy( &p_sys->lock );
178         free( p_sys );
179         return VLC_EGENERIC;
180     }
181
182     return VLC_SUCCESS;
183 }
184
185 /*****************************************************************************
186  * Close: close the plugin
187  *****************************************************************************/
188 static void Close( vlc_object_t *p_this )
189 {
190     filter_t     *p_filter = (filter_t *)p_this;
191     filter_sys_t *p_sys = p_filter->p_sys;
192
193     EqzClean( p_filter );
194     vlc_mutex_destroy( &p_sys->lock );
195     free( p_sys );
196 }
197
198 /*****************************************************************************
199  * DoWork: process samples buffer
200  *****************************************************************************
201  *
202  *****************************************************************************/
203 static block_t * DoWork( filter_t * p_filter, block_t * p_in_buf )
204 {
205     EqzFilter( p_filter, (float*)p_in_buf->p_buffer,
206                (float*)p_in_buf->p_buffer, p_in_buf->i_nb_samples,
207                aout_FormatNbChannels( &p_filter->fmt_in.audio ) );
208     return p_in_buf;
209 }
210
211 /*****************************************************************************
212  * Equalizer stuff
213  *****************************************************************************/
214 typedef struct
215 {
216     int   i_band;
217
218     struct
219     {
220         float f_frequency;
221         float f_alpha;
222         float f_beta;
223         float f_gamma;
224     } band[EQZ_BANDS_MAX];
225
226 } eqz_config_t;
227
228 /* Value from equ-xmms */
229 static const eqz_config_t eqz_config_44100_10b =
230 {
231     10,
232     {
233         {    60, 0.003013, 0.993973, 1.993901 },
234         {   170, 0.008490, 0.983019, 1.982437 },
235         {   310, 0.015374, 0.969252, 1.967331 },
236         {   600, 0.029328, 0.941343, 1.934254 },
237         {  1000, 0.047918, 0.904163, 1.884869 },
238         {  3000, 0.130408, 0.739184, 1.582718 },
239         {  6000, 0.226555, 0.546889, 1.015267 },
240         { 12000, 0.344937, 0.310127, -0.181410 },
241         { 14000, 0.366438, 0.267123, -0.521151 },
242         { 16000, 0.379009, 0.241981, -0.808451 },
243     }
244 };
245 static const eqz_config_t eqz_config_48000_10b =
246 {
247     10,
248     {
249         {    60, 0.002769, 0.994462, 1.994400 },
250         {   170, 0.007806, 0.984388, 1.983897 },
251         {   310, 0.014143, 0.971714, 1.970091 },
252         {   600, 0.027011, 0.945978, 1.939979 },
253         {  1000, 0.044203, 0.911595, 1.895241 },
254         {  3000, 0.121223, 0.757553, 1.623767 },
255         {  6000, 0.212888, 0.574224, 1.113145 },
256         { 12000, 0.331347, 0.337307, 0.000000 },
257         { 14000, 0.355263, 0.289473, -0.333740 },
258         { 16000, 0.371900, 0.256201, -0.628100 }
259     }
260 };
261
262 static inline float EqzConvertdB( float db )
263 {
264     /* Map it to gain,
265      * (we do as if the input of iir is /EQZ_IN_FACTOR, but in fact it's the non iir data that is *EQZ_IN_FACTOR)
266      * db = 20*log( out / in ) with out = in + amp*iir(i/EQZ_IN_FACTOR)
267      * or iir(i) == i for the center freq so
268      * db = 20*log( 1 + amp/EQZ_IN_FACTOR )
269      * -> amp = EQZ_IN_FACTOR*(10^(db/20) - 1)
270      **/
271
272     if( db < -20.0 )
273         db = -20.0;
274     else if(  db > 20.0 )
275         db = 20.0;
276     return EQZ_IN_FACTOR * ( pow( 10, db / 20.0 ) - 1.0 );
277 }
278
279 static int EqzInit( filter_t *p_filter, int i_rate )
280 {
281     filter_sys_t *p_sys = p_filter->p_sys;
282     const eqz_config_t *p_cfg;
283     int i, ch;
284     vlc_value_t val1, val2, val3;
285     vlc_object_t *p_aout = p_filter->p_parent;
286     int i_ret = VLC_ENOMEM;
287
288     /* Select the config */
289     if( i_rate == 48000 )
290     {
291         p_cfg = &eqz_config_48000_10b;
292     }
293     else if( i_rate == 44100 )
294     {
295         p_cfg = &eqz_config_44100_10b;
296     }
297     else
298     {
299         /* TODO compute the coeffs on the fly */
300         msg_Err( p_filter, "rate not supported" );
301         return VLC_EGENERIC;
302     }
303
304     /* Create the static filter config */
305     p_sys->i_band = p_cfg->i_band;
306     p_sys->f_alpha = malloc( p_sys->i_band * sizeof(float) );
307     p_sys->f_beta  = malloc( p_sys->i_band * sizeof(float) );
308     p_sys->f_gamma = malloc( p_sys->i_band * sizeof(float) );
309     if( !p_sys->f_alpha || !p_sys->f_beta || !p_sys->f_gamma )
310         goto error;
311
312     for( i = 0; i < p_sys->i_band; i++ )
313     {
314         p_sys->f_alpha[i] = p_cfg->band[i].f_alpha;
315         p_sys->f_beta[i]  = p_cfg->band[i].f_beta;
316         p_sys->f_gamma[i] = p_cfg->band[i].f_gamma;
317     }
318
319     /* Filter dyn config */
320     p_sys->b_2eqz = false;
321     p_sys->f_gamp = 1.0;
322     p_sys->f_amp  = malloc( p_sys->i_band * sizeof(float) );
323     if( !p_sys->f_amp )
324         goto error;
325
326     for( i = 0; i < p_sys->i_band; i++ )
327     {
328         p_sys->f_amp[i] = 0.0;
329     }
330
331     /* Filter state */
332     for( ch = 0; ch < 32; ch++ )
333     {
334         p_sys->x[ch][0]  =
335         p_sys->x[ch][1]  =
336         p_sys->x2[ch][0] =
337         p_sys->x2[ch][1] = 0.0;
338
339         for( i = 0; i < p_sys->i_band; i++ )
340         {
341             p_sys->y[ch][i][0]  =
342             p_sys->y[ch][i][1]  =
343             p_sys->y2[ch][i][0] =
344             p_sys->y2[ch][i][1] = 0.0;
345         }
346     }
347
348     var_Create( p_aout, "equalizer-bands", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
349     var_Create( p_aout, "equalizer-preset", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
350
351     p_sys->b_2eqz = var_CreateGetBool( p_aout, "equalizer-2pass" );
352
353     var_Create( p_aout, "equalizer-preamp", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
354
355     /* Get initial values */
356     var_Get( p_aout, "equalizer-preset", &val1 );
357     var_Get( p_aout, "equalizer-bands", &val2 );
358     var_Get( p_aout, "equalizer-preamp", &val3 );
359
360     p_sys->b_first = true;
361     PresetCallback( VLC_OBJECT( p_aout ), NULL, val1, val1, p_sys );
362     BandsCallback(  VLC_OBJECT( p_aout ), NULL, val2, val2, p_sys );
363     PreampCallback( VLC_OBJECT( p_aout ), NULL, val3, val3, p_sys );
364     p_sys->b_first = false;
365
366     free( val1.psz_string );
367
368     /* Register preset bands (for intf) if : */
369     /* We have no bands info --> the preset info must be given to the intf */
370     /* or The bands info matches the preset */
371     if (p_sys->psz_newbands == NULL)
372     {
373         msg_Err(p_filter, "No preset selected");
374         free( val2.psz_string );
375         free( p_sys->f_amp );
376         i_ret = VLC_EGENERIC;
377         goto error;
378     }
379     if( ( *(val2.psz_string) &&
380         strstr( p_sys->psz_newbands, val2.psz_string ) ) || !*val2.psz_string )
381     {
382         var_SetString( p_aout, "equalizer-bands", p_sys->psz_newbands );
383         if( p_sys->f_newpreamp == p_sys->f_gamp )
384             var_SetFloat( p_aout, "equalizer-preamp", p_sys->f_newpreamp );
385     }
386     free( val2.psz_string );
387
388     /* Add our own callbacks */
389     var_AddCallback( p_aout, "equalizer-preset", PresetCallback, p_sys );
390     var_AddCallback( p_aout, "equalizer-bands", BandsCallback, p_sys );
391     var_AddCallback( p_aout, "equalizer-preamp", PreampCallback, p_sys );
392     var_AddCallback( p_aout, "equalizer-2pass", TwoPassCallback, p_sys );
393
394     msg_Dbg( p_filter, "equalizer loaded for %d Hz with %d bands %d pass",
395                         i_rate, p_sys->i_band, p_sys->b_2eqz ? 2 : 1 );
396     for( i = 0; i < p_sys->i_band; i++ )
397     {
398         msg_Dbg( p_filter, "   %d Hz -> factor:%f alpha:%f beta:%f gamma:%f",
399                  (int)p_cfg->band[i].f_frequency, p_sys->f_amp[i],
400                  p_sys->f_alpha[i], p_sys->f_beta[i], p_sys->f_gamma[i]);
401     }
402     return VLC_SUCCESS;
403
404 error:
405     free( p_sys->f_alpha );
406     free( p_sys->f_beta );
407     free( p_sys->f_gamma );
408     return i_ret;
409 }
410
411 static void EqzFilter( filter_t *p_filter, float *out, float *in,
412                        int i_samples, int i_channels )
413 {
414     filter_sys_t *p_sys = p_filter->p_sys;
415     int i, ch, j;
416
417     vlc_mutex_lock( &p_sys->lock );
418     for( i = 0; i < i_samples; i++ )
419     {
420         for( ch = 0; ch < i_channels; ch++ )
421         {
422             const float x = in[ch];
423             float o = 0.0;
424
425             for( j = 0; j < p_sys->i_band; j++ )
426             {
427                 float y = p_sys->f_alpha[j] * ( x - p_sys->x[ch][1] ) +
428                           p_sys->f_gamma[j] * p_sys->y[ch][j][0] -
429                           p_sys->f_beta[j]  * p_sys->y[ch][j][1];
430
431                 p_sys->y[ch][j][1] = p_sys->y[ch][j][0];
432                 p_sys->y[ch][j][0] = y;
433
434                 o += y * p_sys->f_amp[j];
435             }
436             p_sys->x[ch][1] = p_sys->x[ch][0];
437             p_sys->x[ch][0] = x;
438
439             /* Second filter */
440             if( p_sys->b_2eqz )
441             {
442                 const float x2 = EQZ_IN_FACTOR * x + o;
443                 o = 0.0;
444                 for( j = 0; j < p_sys->i_band; j++ )
445                 {
446                     float y = p_sys->f_alpha[j] * ( x2 - p_sys->x2[ch][1] ) +
447                               p_sys->f_gamma[j] * p_sys->y2[ch][j][0] -
448                               p_sys->f_beta[j]  * p_sys->y2[ch][j][1];
449
450                     p_sys->y2[ch][j][1] = p_sys->y2[ch][j][0];
451                     p_sys->y2[ch][j][0] = y;
452
453                     o += y * p_sys->f_amp[j];
454                 }
455                 p_sys->x2[ch][1] = p_sys->x2[ch][0];
456                 p_sys->x2[ch][0] = x2;
457
458                 /* We add source PCM + filtered PCM */
459                 out[ch] = p_sys->f_gamp *( EQZ_IN_FACTOR * x2 + o );
460             }
461             else
462             {
463                 /* We add source PCM + filtered PCM */
464                 out[ch] = p_sys->f_gamp *( EQZ_IN_FACTOR * x + o );
465             }
466         }
467
468         in  += i_channels;
469         out += i_channels;
470     }
471     vlc_mutex_unlock( &p_sys->lock );
472 }
473
474 static void EqzClean( filter_t *p_filter )
475 {
476     filter_sys_t *p_sys = p_filter->p_sys;
477     vlc_object_t *p_aout = p_filter->p_parent;
478
479     var_DelCallback( p_aout, "equalizer-bands", BandsCallback, p_sys );
480     var_DelCallback( p_aout, "equalizer-preset", PresetCallback, p_sys );
481     var_DelCallback( p_aout, "equalizer-preamp", PreampCallback, p_sys );
482     var_DelCallback( p_aout, "equalizer-2pass", TwoPassCallback, p_sys );
483
484     free( p_sys->f_alpha );
485     free( p_sys->f_beta );
486     free( p_sys->f_gamma );
487
488     free( p_sys->f_amp );
489     free( p_sys->psz_newbands );
490 }
491
492
493 static int PresetCallback( vlc_object_t *p_aout, char const *psz_cmd,
494                          vlc_value_t oldval, vlc_value_t newval, void *p_data )
495 {
496     VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
497     filter_sys_t *p_sys = p_data;
498
499     const char *psz_preset = newval.psz_string;
500
501     vlc_mutex_lock( &p_sys->lock );
502     if( !*psz_preset || p_sys->i_band != 10 )
503     {
504         vlc_mutex_unlock( &p_sys->lock );
505         return VLC_SUCCESS;
506     }
507
508     for( unsigned i = 0; eqz_preset_10b[i] != NULL; i++ )
509     {
510         if( !strcasecmp( eqz_preset_10b[i]->psz_name, psz_preset ) )
511         {
512             char *psz_newbands = NULL;
513
514             p_sys->f_gamp *= pow( 10, eqz_preset_10b[i]->f_preamp / 20.0 );
515             for( int j = 0; j < p_sys->i_band; j++ )
516             {
517                 lldiv_t d;
518                 char *psz;
519
520                 p_sys->f_amp[j] = EqzConvertdB( eqz_preset_10b[i]->f_amp[j] );
521                 d = lldiv( eqz_preset_10b[i]->f_amp[j] * 10000000, 10000000 );
522                 if( asprintf( &psz, "%s %lld.%07llu",
523                               psz_newbands ? psz_newbands : "",
524                               d.quot, d.rem ) == -1 )
525                 {
526                     free( psz_newbands );
527                     vlc_mutex_unlock( &p_sys->lock );
528                     return VLC_ENOMEM;
529                 }
530                 free( psz_newbands );
531                 psz_newbands = psz;
532             }
533             if( p_sys->b_first == false )
534             {
535                 vlc_mutex_unlock( &p_sys->lock );
536                 var_SetString( p_aout, "equalizer-bands", psz_newbands );
537                 var_SetFloat( p_aout, "equalizer-preamp",
538                               eqz_preset_10b[i]->f_preamp );
539                 free( psz_newbands );
540             }
541             else
542             {
543                 p_sys->psz_newbands = psz_newbands;
544                 p_sys->f_newpreamp = eqz_preset_10b[i]->f_preamp;
545                 vlc_mutex_unlock( &p_sys->lock );
546             }
547             return VLC_SUCCESS;
548         }
549     }
550     vlc_mutex_unlock( &p_sys->lock );
551     msg_Err( p_aout, "equalizer preset '%s' not found", psz_preset );
552     msg_Info( p_aout, "full list:" );
553     for( unsigned i = 0; eqz_preset_10b[i] != NULL; i++ )
554          msg_Info( p_aout, "  - '%s'", eqz_preset_10b[i]->psz_name );
555     return VLC_SUCCESS;
556 }
557
558 static int PreampCallback( vlc_object_t *p_this, char const *psz_cmd,
559                          vlc_value_t oldval, vlc_value_t newval, void *p_data )
560 {
561     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
562     filter_sys_t *p_sys = p_data;
563
564     if( newval.f_float < -20.0 )
565         newval.f_float = -20.0;
566     else if( newval.f_float > 20.0 )
567         newval.f_float = 20.0;
568
569     vlc_mutex_lock( &p_sys->lock );
570     p_sys->f_gamp = pow( 10, newval.f_float /20.0);
571     vlc_mutex_unlock( &p_sys->lock );
572
573     return VLC_SUCCESS;
574 }
575
576 static int BandsCallback( vlc_object_t *p_this, char const *psz_cmd,
577                          vlc_value_t oldval, vlc_value_t newval, void *p_data )
578 {
579     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
580     filter_sys_t *p_sys = p_data;
581     const char *psz_bands = newval.psz_string;
582     const char *p = psz_bands;
583     char *psz_next;
584
585     /* Same thing for bands */
586     vlc_mutex_lock( &p_sys->lock );
587     for( int i = 0; i < p_sys->i_band; i++ )
588     {
589         float f;
590
591         if( *psz_bands == '\0' )
592             break;
593
594         /* Read dB -20/20 */
595         f = us_strtof( p, &psz_next );
596
597         if( psz_next == p )
598             break; /* no conversion */
599
600         p_sys->f_amp[i] = EqzConvertdB( f );
601
602         if( *psz_next == '\0' )
603             break; /* end of line */
604         p = &psz_next[1];
605     }
606     vlc_mutex_unlock( &p_sys->lock );
607     return VLC_SUCCESS;
608 }
609 static int TwoPassCallback( vlc_object_t *p_this, char const *psz_cmd,
610                             vlc_value_t oldval, vlc_value_t newval, void *p_data )
611 {
612     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
613     filter_sys_t *p_sys = p_data;
614
615     vlc_mutex_lock( &p_sys->lock );
616     p_sys->b_2eqz = newval.b_bool;
617     vlc_mutex_unlock( &p_sys->lock );
618     return VLC_SUCCESS;
619 }
620