replace link to cvs snapshot by git snapshot
[paraslash.git] / compress.c
1 /*
2  * Copyright (C) 2005-2006 Andre Noll <maan@systemlinux.org>
3  *
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  *
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  *
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */
18
19 /** \file compress.c paraslash's dynamic audio range compressor */
20
21 /*
22  * Based on AudioCompress, (C) 2002-2004  M. Hari Nezumi <magenta@trikuare.cx>
23  */
24
25 #include "gcc-compat.h"
26 #include "para.h"
27 #include "compress_filter.cmdline.h"
28 #include "list.h"
29 #include "filter.h"
30 #include "string.h"
31
32 /** how fine-grained the gain is */
33 #define GAINSHIFT 10
34 /** the size of the output data buffer */
35 #define COMPRESS_CHUNK_SIZE 40960
36
37 /** data specific to the compress filter */
38 struct private_compress_data {
39         /** an array holding the previous peak values */
40         int *peaks;
41         /** current bucket number to be modified */
42         unsigned pn;
43         /** number of times clipping occured */
44         unsigned clip;
45         /** the current multiplier */
46         int current_gain;
47         /** the target multiplier */
48         int target_gain;
49         /** pointer to the configuration data for this instance of the compress filter */
50         struct gengetopt_args_info *conf;
51 };
52
53 static ssize_t compress(char *inbuf, size_t inbuf_len, struct filter_node *fn)
54 {
55         int16_t *audio = (int16_t *) inbuf, *ip = audio, *op;
56         int peak = 1, pos = 0, i, gr, gf, gn;
57         size_t length = MIN((inbuf_len / 2) * 2, (fn->bufsize - fn->loaded) / 2 * 2);
58         struct private_compress_data *pcd = fn->private_data;
59
60         if (!length)
61                 return 0;
62         /* determine peak's value and position */
63         for (i = 0; i < length / 2; i++, ip++) {
64                 int val = ABS(*ip);
65                 if (val > peak) {
66                         peak = val;
67                         pos = i;
68                 }
69         }
70         pcd->peaks[pcd->pn] = peak;
71         for (i = 0; i < pcd->conf->buckets_arg; i++) {
72                 if (pcd->peaks[i] > peak) {
73                         peak = pcd->peaks[i];
74                         pos = 0;
75                 }
76         }
77         /* determine target gain */
78         gn = (1 << GAINSHIFT) * pcd->conf->target_level_arg / peak;
79         if (gn < (1 << GAINSHIFT))
80                 gn = 1 << GAINSHIFT;
81         pcd->target_gain = (pcd->target_gain * ((1 << pcd->conf->gain_smooth_arg) - 1) + gn)
82                 >> pcd->conf->gain_smooth_arg;
83         /* give it an extra insignificant nudge to counteract possible
84          * rounding error
85          */
86         if (gn < pcd->target_gain)
87                 pcd->target_gain--;
88         else if (gn > pcd->target_gain)
89                 pcd->target_gain++;
90         if (pcd->target_gain > pcd->conf->gain_max_arg << GAINSHIFT)
91                 pcd->target_gain = pcd->conf->gain_max_arg << GAINSHIFT;
92         /* see if a peak is going to clip */
93         gn = (1 << GAINSHIFT) * 32768 / peak;
94         if (gn < pcd->target_gain) {
95                 pcd->target_gain = gn;
96                 if (pcd->conf->anticlip_given)
97                         pos = 0;
98         } else
99                 /* we're ramping up, so draw it out over the whole frame */
100                 pos = length;
101         /* determine gain rate necessary to make target */
102         if (!pos)
103                 pos = 1;
104         gr = ((pcd->target_gain - pcd->current_gain) << 16) / pos;
105         gf = pcd->current_gain << 16;
106         ip = audio;
107         op = (int16_t *)(fn->buf + fn->loaded);
108         for (i = 0; i < length / 2; i++) {
109                 int sample;
110                 /* interpolate the gain */
111                 pcd->current_gain = gf >> 16;
112                 if (i < pos)
113                         gf += gr;
114                 else if (i == pos)
115                         gf = pcd->target_gain << 16;
116                 /* amplify */
117                 sample = (*ip++) * pcd->current_gain * pcd->conf->volume_arg / 10 >> GAINSHIFT;
118                 if (sample < -32768) {
119                         pcd->clip++;
120                         sample = -32768;
121                 } else if (sample > 32767) {
122                         pcd->clip++;
123                         sample = 32767;
124                 }
125                 *op++ = sample;
126         }
127         pcd->pn = (pcd->pn + 1) % pcd->conf->buckets_arg;
128         PARA_DEBUG_LOG("bucket: %03i, input len: %i, length: %i, peak: %05i, "
129                 "current gain: %03i, clipped: %d\n", pcd->pn, inbuf_len,
130                 length, peak, pcd->current_gain, pcd->clip);
131         fn->loaded = length;
132         return length;
133 }
134
135 static void close_compress(struct filter_node *fn)
136 {
137         struct private_compress_data *pcd = fn->private_data;
138         free(pcd->peaks);
139         free(fn->private_data);
140         free(fn->buf);
141 }
142
143 static void *compress_parse_config(int argc, char **argv)
144 {
145         struct gengetopt_args_info *ret = para_calloc(sizeof(struct gengetopt_args_info));
146         if (!compress_cmdline_parser(argc, argv, ret))
147                 return ret;
148         free(ret);
149         return NULL;
150 }
151
152 static void open_compress(struct filter_node *fn)
153 {
154         struct private_compress_data *pcd = para_calloc(
155                 sizeof(struct private_compress_data));
156 //      compress_cmdline_parser(fn->argc, fn->argv, &pcd->conf);
157         pcd->conf = fn->conf;
158         pcd->peaks = para_calloc(pcd->conf->buckets_arg * sizeof(int));
159         fn->private_data = pcd;
160         fn->bufsize = COMPRESS_CHUNK_SIZE;
161         fn->buf = para_malloc(fn->bufsize);
162         fn->loaded = 0;
163 }
164
165 /** the init function of the compress filter */
166 void compress_init(struct filter *f)
167 {
168         f->open = open_compress;
169         f->close = close_compress;
170         f->convert = compress;
171         f->print_help = compress_cmdline_parser_print_help;
172         f->parse_config = compress_parse_config;
173 }