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