1 #include "accuraterip.h"
2 #include "pcm.h"
3 #include "mod_defs.h"
4 
5 /********************************************************
6  Audio Tools, a module and set of tools for manipulating audio data
7  Copyright (C) 2007-2014  Brian Langenberger
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   Offset checksum calculation adapted from Jon Lund Steffensen's work:
26 
27   http://jonls.dk/2009/10/calculating-accuraterip-checksums/
28 
29   The math is the same, but I find it clearer to store the initial
30   and trailing values used to adjust the values sum in a seperate memory
31   space rather than stuff them in the checksums area temporarily.
32  **********************************************************************/
33 
34 static PyMethodDef accuraterip_methods[] = {
35     {NULL, NULL, 0, NULL}        /* Sentinel */
36 };
37 
MOD_INIT(_accuraterip)38 MOD_INIT(_accuraterip)
39 {
40     PyObject* m;
41 
42     MOD_DEF(m, "_accuraterip",
43             "an AccurateRip checksum calculation module",
44             accuraterip_methods)
45 
46     accuraterip_ChecksumType.tp_new = PyType_GenericNew;
47     if (PyType_Ready(&accuraterip_ChecksumType) < 0)
48         return MOD_ERROR_VAL;
49 
50     Py_INCREF(&accuraterip_ChecksumType);
51     PyModule_AddObject(m, "Checksum",
52                        (PyObject *)&accuraterip_ChecksumType);
53 
54     return MOD_SUCCESS_VAL(m);
55 }
56 
57 static PyObject*
Checksum_new(PyTypeObject * type,PyObject * args,PyObject * kwds)58 Checksum_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
59 {
60     accuraterip_Checksum *self;
61 
62     self = (accuraterip_Checksum *)type->tp_alloc(type, 0);
63 
64     return (PyObject *)self;
65 }
66 
67 int
Checksum_init(accuraterip_Checksum * self,PyObject * args,PyObject * kwds)68 Checksum_init(accuraterip_Checksum *self, PyObject *args, PyObject *kwds)
69 {
70     static char *kwlist[] = {"total_pcm_frames",
71                              "sample_rate",
72                              "is_first",
73                              "is_last",
74                              "pcm_frame_range",
75                              "accurateripv2_offset",
76                              NULL};
77 
78     PyObject *pcm;
79     int total_pcm_frames;
80     int sample_rate = 44100;
81     int is_first = 0;
82     int is_last = 0;
83     int pcm_frame_range = 1;
84     int accurateripv2_offset = 0;
85 
86     self->accuraterip_v1.checksums = NULL;
87     self->accuraterip_v1.initial_values = NULL;
88     self->accuraterip_v1.final_values = NULL;
89     self->framelist_class = NULL;
90 
91     if (!PyArg_ParseTupleAndKeywords(args, kwds, "i|iiiii", kwlist,
92                                      &total_pcm_frames,
93                                      &sample_rate,
94                                      &is_first,
95                                      &is_last,
96                                      &pcm_frame_range,
97                                      &accurateripv2_offset))
98         return -1;
99 
100     if (total_pcm_frames > 0) {
101         self->total_pcm_frames = total_pcm_frames;
102     } else {
103         PyErr_SetString(PyExc_ValueError, "total PCM frames must be > 0");
104         return -1;
105     }
106 
107     if (sample_rate > 0) {
108         if (is_first) {
109             self->start_offset = ((sample_rate / 75) * 5);
110         } else {
111             self->start_offset = 1;
112         }
113         if (is_last) {
114             const int offset = (total_pcm_frames - ((sample_rate / 75) * 5));
115             if (offset >= 0) {
116                 self->end_offset = offset;
117             } else {
118                 self->end_offset = 0;
119             }
120         } else {
121             self->end_offset = total_pcm_frames;
122         }
123     } else {
124         PyErr_SetString(PyExc_ValueError, "sample rate must be > 0");
125         return -1;
126     }
127 
128     if (pcm_frame_range <= 0) {
129         PyErr_SetString(PyExc_ValueError, "PCM frame range must be > 0");
130         return -1;
131     }
132 
133     if (accurateripv2_offset < 0) {
134         PyErr_SetString(PyExc_ValueError, "accurateripv2_offset must be >= 0");
135         return -1;
136     }
137 
138     self->pcm_frame_range = pcm_frame_range;
139     self->processed_frames = 0;
140 
141     /*initialize AccurateRip V1 values*/
142     self->accuraterip_v1.index = 1;
143     self->accuraterip_v1.checksums = calloc(pcm_frame_range, sizeof(uint32_t));
144     self->accuraterip_v1.initial_values = init_queue(pcm_frame_range - 1);
145     self->accuraterip_v1.final_values = init_queue(pcm_frame_range - 1);
146     self->accuraterip_v1.values_sum = 0;
147 
148 
149     /*initialize AccurateRip V2 values*/
150     self->accuraterip_v2.index = 1;
151     self->accuraterip_v2.checksum = 0;
152     self->accuraterip_v2.current_offset = accurateripv2_offset;
153     self->accuraterip_v2.initial_offset = accurateripv2_offset;
154 
155     /*keep a copy of the FrameList class so we can check for it*/
156     if ((pcm = PyImport_ImportModule("audiotools.pcm")) == NULL)
157         return -1;
158     self->framelist_class = PyObject_GetAttrString(pcm, "FrameList");
159     Py_DECREF(pcm);
160     if (self->framelist_class == NULL) {
161         return -1;
162     }
163 
164     return 0;
165 }
166 
167 void
Checksum_dealloc(accuraterip_Checksum * self)168 Checksum_dealloc(accuraterip_Checksum *self)
169 {
170     free(self->accuraterip_v1.checksums);
171     free_queue(self->accuraterip_v1.initial_values);
172     free_queue(self->accuraterip_v1.final_values);
173 
174     Py_XDECREF(self->framelist_class);
175 
176     Py_TYPE(self)->tp_free((PyObject*)self);
177 }
178 
179 static inline unsigned
unsigned_(int v)180 unsigned_(int v)
181 {
182     return (unsigned)((v >= 0) ? v : ((1 << 16) - (-v)));
183 }
184 
185 static inline unsigned
value(int l,int r)186 value(int l, int r)
187 {
188     return (unsigned_(r) << 16) | unsigned_(l);
189 }
190 
191 static PyObject*
Checksum_update(accuraterip_Checksum * self,PyObject * args)192 Checksum_update(accuraterip_Checksum* self, PyObject *args)
193 {
194     pcm_FrameList *framelist;
195     const unsigned channels = 2;
196     unsigned i;
197 
198     if (!PyArg_ParseTuple(args, "O!", self->framelist_class, &framelist))
199         return NULL;
200 
201     /*ensure FrameList is CD-formatted*/
202     if (framelist->channels != 2) {
203         PyErr_SetString(PyExc_ValueError,
204                         "FrameList must be 2 channels");
205         return NULL;
206     }
207     if (framelist->bits_per_sample != 16) {
208         PyErr_SetString(PyExc_ValueError,
209                         "FrameList must be 16 bits per sample");
210         return NULL;
211     }
212 
213     /*ensure we're not given too many samples*/
214     if ((self->processed_frames + framelist->frames) >
215         (self->total_pcm_frames + self->pcm_frame_range - 1)) {
216         PyErr_SetString(PyExc_ValueError, "too many samples for checksum");
217         return NULL;
218     }
219 
220     /*update checksum values*/
221     for (i = 0; i < framelist->frames; i++) {
222         const unsigned v = value(framelist->samples[i * channels],
223                                  framelist->samples[i * channels + 1]);
224         update_frame_v1(&(self->accuraterip_v1),
225                         self->total_pcm_frames,
226                         self->start_offset,
227                         self->end_offset,
228                         v);
229         update_frame_v2(&(self->accuraterip_v2),
230                         self->total_pcm_frames,
231                         self->start_offset,
232                         self->end_offset,
233                         v);
234     }
235 
236     self->processed_frames += framelist->frames;
237 
238     Py_INCREF(Py_None);
239     return Py_None;
240 }
241 
242 static void
update_frame_v1(struct accuraterip_v1 * v1,unsigned total_pcm_frames,unsigned start_offset,unsigned end_offset,unsigned value)243 update_frame_v1(struct accuraterip_v1 *v1,
244                 unsigned total_pcm_frames,
245                 unsigned start_offset,
246                 unsigned end_offset,
247                 unsigned value)
248 {
249     /*calculate initial checksum*/
250     if ((v1->index >= start_offset) && (v1->index <= end_offset)) {
251         v1->checksums[0] += (value * v1->index);
252         v1->values_sum += value;
253     }
254 
255     /*store the first (pcm_frame_range - 1) values in initial_values*/
256     if ((v1->index >= start_offset) && (!queue_full(v1->initial_values))) {
257         queue_push(v1->initial_values, value);
258     }
259 
260     /*store the trailing (pcm_frame_range - 1) values in final_values*/
261     if ((v1->index > end_offset) && (!queue_full(v1->final_values))) {
262         queue_push(v1->final_values, value);
263     }
264 
265     /*calculate incremental checksums*/
266     if (v1->index > total_pcm_frames) {
267         const uint32_t initial_value = queue_pop(v1->initial_values);
268 
269         const uint32_t final_value = queue_pop(v1->final_values);
270 
271         const uint32_t initial_value_product =
272             (uint32_t)(start_offset - 1) * initial_value;
273 
274         const uint32_t final_value_product =
275             (uint32_t)end_offset * final_value;
276 
277         v1->checksums[v1->index - total_pcm_frames] =
278             v1->checksums[v1->index - total_pcm_frames - 1] +
279             final_value_product -
280             v1->values_sum -
281             initial_value_product;
282 
283         v1->values_sum -= initial_value;
284         v1->values_sum += final_value;
285     }
286 
287     v1->index++;
288 }
289 
290 static void
update_frame_v2(struct accuraterip_v2 * v2,unsigned total_pcm_frames,unsigned start_offset,unsigned end_offset,unsigned value)291 update_frame_v2(struct accuraterip_v2 *v2,
292                 unsigned total_pcm_frames,
293                 unsigned start_offset,
294                 unsigned end_offset,
295                 unsigned value)
296 {
297     if (!v2->current_offset) {
298         if ((v2->index >= start_offset) && (v2->index <= end_offset)) {
299             const uint64_t v_i = ((uint64_t)value * (uint64_t)(v2->index));
300             v2->checksum += (uint32_t)(v_i >> 32);
301         }
302         v2->index++;
303     } else {
304         v2->current_offset--;
305     }
306 }
307 
308 static PyObject*
Checksum_checksums_v1(accuraterip_Checksum * self,PyObject * args)309 Checksum_checksums_v1(accuraterip_Checksum* self, PyObject *args)
310 {
311     const struct accuraterip_v1 *v1 = &(self->accuraterip_v1);
312     unsigned i;
313 
314     if (self->processed_frames <
315         (self->total_pcm_frames + self->pcm_frame_range - 1)) {
316         PyErr_SetString(PyExc_ValueError, "insufficient samples for checksums");
317         return NULL;
318     }
319 
320     PyObject *checksums_obj = PyList_New(0);
321     if (checksums_obj == NULL)
322         return NULL;
323 
324     for (i = 0; i < self->pcm_frame_range; i++) {
325         PyObject *number = PyLong_FromUnsignedLong(v1->checksums[i]);
326         int result;
327         if (number == NULL) {
328             Py_DECREF(checksums_obj);
329             return NULL;
330         }
331         result = PyList_Append(checksums_obj, number);
332         Py_DECREF(number);
333         if (result == -1) {
334             Py_DECREF(checksums_obj);
335             return NULL;
336         }
337     }
338 
339     return checksums_obj;
340 }
341 
342 static PyObject*
Checksum_checksum_v2(accuraterip_Checksum * self,PyObject * args)343 Checksum_checksum_v2(accuraterip_Checksum* self, PyObject *args)
344 {
345     const struct accuraterip_v1 *v1 = &(self->accuraterip_v1);
346     const struct accuraterip_v2 *v2 = &(self->accuraterip_v2);
347 
348     if (self->processed_frames <
349         (self->total_pcm_frames + self->pcm_frame_range - 1)) {
350         PyErr_SetString(PyExc_ValueError, "insufficient samples for checksums");
351         return NULL;
352     } else {
353         const uint32_t checksum_v2 =
354             v2->checksum + v1->checksums[v2->initial_offset];
355 
356         return PyLong_FromUnsignedLong(checksum_v2);
357     }
358 }
359 
360 static struct queue*
init_queue(unsigned total_size)361 init_queue(unsigned total_size)
362 {
363     struct queue *queue = malloc(sizeof(struct queue));
364     queue->values = malloc(total_size * sizeof(uint32_t));
365     queue->total_size = total_size;
366     queue->head_index = 0;
367     queue->tail_index = 0;
368     return queue;
369 }
370 
371 static void
free_queue(struct queue * queue)372 free_queue(struct queue *queue)
373 {
374     if (queue != NULL) {
375         free(queue->values);
376         free(queue);
377     }
378 }
379