PageRenderTime 967ms CodeModel.GetById 538ms app.highlight 189ms RepoModel.GetById 233ms app.codeStats 1ms

/external/pysoundtouch14/soundtouchmodule.cpp

http://echo-nest-remix.googlecode.com/
C++ | 690 lines | 494 code | 140 blank | 56 comment | 36 complexity | 048516cc4bbe94d5fc010b94bfe801f5 MD5 | raw file
  1/***************************************************************************
  2 *   Copyright (C) 2006 by Patrick Stinson                                 *
  3 *   patrickkidd@gmail.com                                                 *
  4 *                                                                         *
  5 *   Permission is hereby granted, free of charge, to any person obtaining *
  6 *   a copy of this software and associated documentation files (the       *
  7 *   "Software"), to deal in the Software without restriction, including   *
  8 *   without limitation the rights to use, copy, modify, merge, publish,   *
  9 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 10 *   permit persons to whom the Software is furnished to do so, subject to *
 11 *   the following conditions:                                             *
 12 *                                                                         *
 13 *   The above copyright notice and this permission notice shall be        *
 14 *   included in all copies or substantial portions of the Software.       *
 15 *                                                                         *
 16 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 17 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 18 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
 19 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 20 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 21 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 22 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 23 ***************************************************************************/
 24
 25#include <Python.h>
 26#include <numpy/arrayobject.h>
 27#include <numpy/libnumarray.h>
 28#include <iostream>
 29#include <stdexcept>
 30#include "libsoundtouch/SoundTouch.h"
 31#include "libsoundtouch/BPMDetect.h"
 32
 33 
 34static PyObject *SoundTouchError;
 35
 36
 37#ifndef Py_RETURN_NONE
 38#define Py_RETURN_NONE Py_INCREF(Py_None); return Py_None;
 39#endif
 40
 41
 42/***************************************************************************
 43 *  class SoundTouch
 44 ***************************************************************************/
 45
 46class SoundTouchProxy : public soundtouch::SoundTouch
 47{
 48public:
 49  float _getrate(){return rate;}
 50  float _gettempo(){return tempo;}
 51  uint _getchannels(){return channels;}
 52};
 53
 54
 55typedef struct {
 56  PyObject_HEAD
 57  SoundTouchProxy *soundtouch;
 58} SoundTouch;
 59
 60
 61static void
 62SoundTouch_dealloc(SoundTouch *self)
 63{
 64  delete self->soundtouch;
 65  self->ob_type->tp_free((PyObject *) self);
 66}
 67
 68
 69static PyObject *
 70SoundTouch_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 71{
 72  SoundTouch *self;
 73  self = (SoundTouch *) type->tp_alloc(type, 0);
 74  self->soundtouch = new SoundTouchProxy;
 75  self->soundtouch->setChannels(2);
 76  self->soundtouch->setSampleRate(44100);
 77  return (PyObject *) self;
 78}
 79
 80
 81static int SoundTouch_init(SoundTouch *self, PyObject *args, PyObject *kwds)
 82{
 83  return 0;
 84}
 85
 86
 87static PyObject *
 88SoundTouch_setRate(SoundTouch *self, PyObject *args)
 89{
 90  float rate;
 91  if(!PyArg_ParseTuple(args, "f", &rate)) {
 92    return NULL;
 93  }
 94  self->soundtouch->setRate(rate);
 95
 96  Py_RETURN_NONE;
 97}
 98
 99
100static PyObject *
101SoundTouch_setTempo(SoundTouch *self, PyObject *args)
102{
103  float tempo;
104  if(!PyArg_ParseTuple(args, "f", &tempo))
105    return NULL;
106
107  self->soundtouch->setTempo(tempo);
108
109  Py_RETURN_NONE;
110}
111
112
113static PyObject *
114SoundTouch_setRateChange(SoundTouch *self, PyObject *args)
115{
116  float newRate;
117  if(!PyArg_ParseTuple(args, "f", &newRate))
118    return NULL;
119
120  self->soundtouch->setRateChange(newRate);
121
122  Py_RETURN_NONE;
123}
124
125
126static PyObject *
127SoundTouch_setTempoChange(SoundTouch *self, PyObject *args)
128{
129  float newTempo;
130  if(!PyArg_ParseTuple(args, "f", &newTempo))
131    return NULL;
132
133  self->soundtouch->setTempoChange(newTempo);
134
135  Py_RETURN_NONE;
136}
137
138
139static PyObject *
140SoundTouch_setPitch(SoundTouch *self, PyObject *args)
141{
142  float pitch;
143  if(!PyArg_ParseTuple(args, "f", &pitch))
144    return NULL;
145
146  self->soundtouch->setPitch(pitch);
147
148  Py_RETURN_NONE;
149}
150
151
152static PyObject *
153SoundTouch_setPitchOctaves(SoundTouch *self, PyObject *args)
154{
155  float newPitch;
156  if(!PyArg_ParseTuple(args, "f", &newPitch))
157    return NULL;
158
159  self->soundtouch->setPitchOctaves(newPitch);
160
161  Py_RETURN_NONE;
162}
163
164
165static PyObject *
166SoundTouch_setPitchSemiTones(SoundTouch *self, PyObject *args)
167{
168  float newPitch;
169  if(!PyArg_ParseTuple(args, "f", &newPitch))
170    return NULL;
171  self->soundtouch->setPitchSemiTones(newPitch);
172
173  Py_RETURN_NONE;
174}
175
176
177static PyObject *
178SoundTouch_setChannels(SoundTouch *self, PyObject *args)
179{
180  int channels;
181  if(!PyArg_ParseTuple(args, "i", &channels))
182    return NULL;
183
184  try
185    {
186      self->soundtouch->setChannels(channels);
187    }
188  catch(std::runtime_error &error)
189    {
190      PyErr_Format(PyExc_RuntimeError, error.what());
191      return NULL;
192    }
193
194  Py_RETURN_NONE;
195}
196
197
198static PyObject *
199SoundTouch_setSampleRate(SoundTouch *self, PyObject *args)
200{
201  int sampleRate;
202  if(!PyArg_ParseTuple(args, "i", &sampleRate))
203    return NULL;
204
205  self->soundtouch->setSampleRate(sampleRate);
206
207  Py_RETURN_NONE;
208}
209
210
211static PyObject *
212SoundTouch_flush(SoundTouch *self)
213{
214  self->soundtouch->flush();
215
216  Py_RETURN_NONE;
217}
218
219
220static PyObject *
221SoundTouch_clear(SoundTouch *self)
222{
223  self->soundtouch->clear();
224
225  Py_RETURN_NONE;
226}
227
228
229static PyObject *
230SoundTouch_putSamples(SoundTouch *self, PyObject *args)
231{
232  float *samples;
233  PyObject *osound;
234  PyArrayObject *sound;
235  PyObject *typeObject;
236  int type;
237
238  int ret = PyArg_ParseTuple(args, "O", &osound);
239  if(!ret) {
240    return NULL;
241  }
242  /*
243
244  typeObject = PyObject_CallMethod(osound, "type", NULL);
245  
246  type = NA_typeObjectToTypeNo(typeObject);
247  
248  Py_DECREF(typeObject);
249*/  
250  type = tFloat32; // hack for now
251  
252  if(type != tFloat32)
253    {
254      PyErr_Format(SoundTouchError, "sounds must be Float32");
255      return NULL;
256    }
257
258  sound = (PyArrayObject *) NA_InputArray(osound, tFloat32, NUM_C_ARRAY);
259  
260  if(!sound)
261    {
262      Py_XDECREF(sound);
263      PyErr_Format(SoundTouchError, 
264                   "couldn't convert array to PyArrayObject.");
265      return NULL;
266    }
267  else if(sound->nd != 1)
268    {
269      Py_XDECREF(sound);
270      PyErr_Format(SoundTouchError, "sound arrays must have 1 dimension.");
271      return NULL;
272    }
273
274  samples = (Float32 *) NA_OFFSETDATA(sound);
275  uint numSamples = sound->dimensions[0];
276  uint channels = self->soundtouch->_getchannels();
277
278  try
279    {
280      self->soundtouch->putSamples(samples, numSamples / channels);
281    }
282  catch(std::runtime_error &error)
283    {
284      PyErr_Format(PyExc_RuntimeError, error.what());
285      return NULL;
286    }
287
288  Py_RETURN_NONE;
289}
290
291static PyObject *
292SoundTouch_receiveSamples(SoundTouch *self, PyObject *args)
293{
294  float *samples;
295  PyObject *osound;
296  PyArrayObject *sound;
297  int type;
298  PyObject *typeObject;
299
300  if(!PyArg_ParseTuple(args, "O", &osound)) {
301    return NULL;
302  }
303
304  /*typeObject = PyObject_CallMethod(osound, "type", NULL);
305  type = NA_typeObjectToTypeNo(typeObject);
306  Py_DECREF(typeObject);
307  if(type != tFloat32)
308    {
309      PyErr_Format(SoundTouchError, "sounds must be Float32");
310      return NULL;
311    }
312*/
313    type = tFloat32; //hack
314  sound = (PyArrayObject *) NA_InputArray(osound, tFloat32, NUM_C_ARRAY);
315  if(!sound)
316    {
317      Py_XDECREF(sound);
318      PyErr_Format(SoundTouchError, 
319                   "couldn't convert array to PyArrayObject.");
320      return NULL;
321    }
322  else if(sound->nd != 1)
323    {
324      Py_XDECREF(sound);
325      PyErr_Format(SoundTouchError, "sound arrays must have 1 dimension.");
326      return NULL;
327    }
328
329
330  uint length = sound->dimensions[0];
331  uint channels = self->soundtouch->_getchannels();
332  samples = (Float32 *) NA_OFFSETDATA(sound);
333
334  length = self->soundtouch->receiveSamples(samples, length / channels);
335  
336  return Py_BuildValue("i", length);
337}
338
339
340static PyObject *
341SoundTouch_setSetting(SoundTouch *self, PyObject *args)
342{
343  uint settingId;
344  uint value;
345  if(!PyArg_ParseTuple(args, "ii", &settingId, &value))
346    return NULL;
347
348  int ret = self->soundtouch->setSetting(settingId, value) ? 1 : 0;
349  return Py_BuildValue("b", ret);
350}
351
352
353static PyObject *
354SoundTouch_getSetting(SoundTouch *self, PyObject *args)
355{
356  uint settingId;
357
358  if(!PyArg_ParseTuple(args, "i", &settingId))
359    return NULL;
360
361  return Py_BuildValue("i", self->soundtouch->getSetting(settingId));
362}
363
364
365static PyObject *
366SoundTouch_numUnprocessedSamples(SoundTouch *self)
367{
368  return Py_BuildValue("i", self->soundtouch->numUnprocessedSamples());
369}
370
371
372static PyObject *
373SoundTouch_isEmpty(SoundTouch *self)
374{
375  if(self->soundtouch->isEmpty())
376    {
377      Py_INCREF(Py_True);
378      return Py_True;
379    }
380  else
381    {
382      Py_INCREF(Py_False);
383      return Py_False;
384    }
385}
386
387
388static PyObject *
389SoundTouch_numSamples(SoundTouch *self)
390{
391  return Py_BuildValue("i", self->soundtouch->numSamples());
392}
393
394
395/*
396static PyGetSetDef SoundTouch_getseters[] = {
397  {"rate", (getter) SoundTouch_getrate, NULL, 
398   "Effective 'rate' value calculated from 'virtualRate', 'virtualTempo'"
399   "and 'virtualPitch'"},
400  {
401}
402*/
403
404
405
406
407static PyMethodDef SoundTouch_methods[] = {
408  {"setRate", (PyCFunction) SoundTouch_setRate, METH_VARARGS,
409   "Sets new rate control value. Normal rate = 1.0, smaller values"
410   "represent slower rate, larger faster rates."},
411
412  {"setRateChange", (PyCFunction) SoundTouch_setRateChange, METH_VARARGS,
413   "Sets new rate control value as a difference in percents compared"
414   "to the original rate (-50 .. +100 %)"},
415
416  {"setTempo", (PyCFunction) SoundTouch_setTempo, METH_VARARGS,
417   "Sets new tempo control value. Normal tempo = 1.0, smaller values"
418   "represent slower tempo, larger faster tempo."},
419
420  {"setTempoChange", (PyCFunction) SoundTouch_setTempoChange, METH_VARARGS,
421   "Sets new tempo control value as a difference in percents compared"
422   "to the original tempo (-50 .. +100 %)"},
423
424  {"setPitch", (PyCFunction) SoundTouch_setPitch, METH_VARARGS,
425   "Sets new pitch control value. Original pitch = 1.0, smaller values"
426   "represent lower pitches, larger values higher pitch."},
427
428  {"setPitchOctaves", (PyCFunction) SoundTouch_setPitchOctaves, METH_VARARGS,
429   "Sets pitch change in octaves compared to the original pitch"
430   "(-1.00 .. +1.00)"},
431
432  {"setPitchSemiTones", (PyCFunction) SoundTouch_setPitchSemiTones, METH_VARARGS,
433   "Sets pitch change in semi-tones compared to the original pitch"
434   "(-12 .. +12)"},
435
436  {"setChannels", (PyCFunction) SoundTouch_setChannels, METH_VARARGS,
437   "Sets the number of channels, 1 = mono, 2 = stereo"},
438
439  {"setSampleRate", (PyCFunction) SoundTouch_setSampleRate, METH_VARARGS,
440   "Sets sample rate."},
441
442  {"flush", (PyCFunction) SoundTouch_flush, METH_NOARGS,
443   "Flushes the last samples from the processing pipeline to the output.\n"
444   "Clears also the internal processing buffers.\n"
445   "\n"
446   "Note: This function is meant for extracting the last samples of a sound"
447   "stream. This function may introduce additional blank samples in the end"
448   "of the sound stream, and thus it's not recommended to call this function"
449   "in the middle of a sound stream."},
450
451  {"putSamples", (PyCFunction) SoundTouch_putSamples, METH_VARARGS,
452   "Adds 'numSamples' pcs of samples from the 'samples' memory position into"
453   "the input of the object. Notice that sample rate _has_to_ be set before"
454   "calling this function, otherwise throws a runtime_error exception"},
455
456  {"receiveSamples", (PyCFunction) SoundTouch_receiveSamples, METH_VARARGS,
457   "Return samples from beginning of the sample buffer. Copies requested "
458   "samples to output buffer and removes them from the sample buffer. If "
459   "there are less than 'numsample' samples in the buffer, returns all "
460   "that are available."},
461
462  {"clear", (PyCFunction) SoundTouch_clear, METH_VARARGS,
463   "Clears all the samples in the object's output and internal processing"
464   "buffers."},
465
466  {"setSetting", (PyCFunction) SoundTouch_setSetting, METH_VARARGS,
467   "Changes a setting controlling the processing system behaviour. See the"
468   "'SETTING_...' defines for available setting ID's.\n"
469   "\n"
470   "return 'TRUE' if the setting was succesfully changed."},
471
472  {"getSetting", (PyCFunction) SoundTouch_getSetting, METH_VARARGS,
473   "Reads a setting controlling the processing system behaviour. See the"
474   "'SETTING_...' defines for available setting ID's.\n"
475   "\n"
476   "return the setting value."},
477
478  {"numUnprocessedSamples", (PyCFunction) SoundTouch_numUnprocessedSamples, METH_VARARGS,
479   "Returns number of samples currently unprocessed."},
480
481  /***************** FIFOSampleBuffer ***************************************/
482  {"isEmpty", (PyCFunction) SoundTouch_isEmpty, METH_NOARGS,
483   "Returns True if the sample buffer is empty."},
484
485  {"numSamples", (PyCFunction) SoundTouch_numSamples, METH_NOARGS,
486   "Returns the number of samples currently in the buffer."},
487
488  {NULL} /* Sentinel */
489};
490
491
492static char * SoundTouch_doc = 
493" SoundTouch - main class for tempo/pitch/rate adjusting routines.\n"
494"\n"
495" Notes:\n"
496" - Initialize the SoundTouch object instance by setting up the sound stream\n"
497"   parameters with functions 'setSampleRate' and 'setChannels', then set\n"
498"   desired tempo/pitch/rate settings with the corresponding functions.\n"
499"\n"
500" - The SoundTouch class behaves like a first-in-first-out pipeline: The\n"
501"   samples that are to be processed are fed into one of the pipe by calling\n"
502"   function 'putSamples', while the ready processed samples can be read\n"
503"   from the other end of the pipeline with function 'receiveSamples'.\n"
504"\n"
505" - The SoundTouch processing classes require certain sized 'batches' of\n"
506"   samples in order to process the sound. For this reason the classes buffer\n"
507"   incoming samples until there are enough of samples available for\n"
508"   processing, then they carry out the processing step and consequently\n"
509"   make the processed samples available for outputting.\n"
510"\n"
511" - For the above reason, the processing routines introduce a certain\n"
512"   'latency' between the input and output, so that the samples input to\n"
513"   SoundTouch may not be immediately available in the output, and neither\n"
514"   the amount of outputtable samples may not immediately be in direct\n"
515"   relationship with the amount of previously input samples.\n"
516"\n"
517" - The tempo/pitch/rate control parameters can be altered during processing.\n"
518"   Please notice though that they aren't currently protected by semaphores,\n"
519"   so in multi-thread application external semaphore protection may be\n"
520"   required.\n"
521"\n"
522" - This class utilizes classes 'TDStretch' for tempo change (without modifying\n"
523"   pitch) and 'RateTransposer' for changing the playback rate (that is, both\n"
524"   tempo and pitch in the same ratio) of the sound. The third available control\n"
525"   'pitch' (change pitch but maintain tempo) is produced by a combination of\n"
526"   combining the two other controls.\n"
527"\n"
528"Other handy functions that are implemented in the ancestor classes (see\n"
529"classes 'FIFOProcessor' and 'FIFOSamplePipe')\n"
530"\n"
531" - receiveSamples() : Use this function to receive 'ready' processed samples from SoundTouch.\n"
532" - numSamples()     : Get number of 'ready' samples that can be received with\n"
533"                      function 'receiveSamples()'\n"
534" - isEmpty()        : Returns nonzero if there aren't any 'ready' samples.\n"
535" - clear()          : Clears all samples from ready/processing buffers.\n";
536
537
538static PyTypeObject SoundTouchType = {
539  PyObject_HEAD_INIT(NULL)
540  0,                         /*ob_size*/
541  "soundtouch.SoundTouch",   /*tp_name*/
542  sizeof(SoundTouch), /*tp_basicsize*/
543  0,                         /*tp_itemsize*/
544  (destructor) SoundTouch_dealloc,                         /*tp_dealloc*/
545  0,                         /*tp_print*/
546  0,                         /*tp_getattr*/
547  0,                         /*tp_setattr*/
548  0,                         /*tp_compare*/
549  0,                         /*tp_repr*/
550  0,                         /*tp_as_number*/
551  0,                         /*tp_as_sequence*/
552  0,                         /*tp_as_mapping*/
553  0,                         /*tp_hash */
554  0,                         /*tp_call*/
555  0,                         /*tp_str*/
556  0,                         /*tp_getattro*/
557  0,                         /*tp_setattro*/
558  0,                         /*tp_as_buffer*/
559  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /*tp_flags*/
560  SoundTouch_doc,      /* tp_doc */
561  0,                         /* tp_traverse */
562  0,                         /* tp_clear */
563  0,                         /* tp_richcompare */
564  0,                         /* tp_weaklistoffset */
565  0,                         /* tp_iter */
566  0,                         /* tp_iternext */
567  SoundTouch_methods,        /* tp_methods */
568  0,        /* tp_members */
569  0,                         /* tp_getset */
570  0,                         /* tp_base */
571  0,                         /* tp_dict */
572  0,                         /* tp_descr_get */
573  0,                         /* tp_descr_set */
574  0,                         /* tp_dictoffset */
575  (initproc)SoundTouch_init, /* tp_init */
576  0,                         /* tp_alloc */
577  SoundTouch_new,              /* tp_new */
578};
579
580
581/***************************************************************************
582 * module parts
583 **************************************************************************/
584
585
586static PyObject *
587soundtouch_find_bpm(PyObject *obj, PyObject *args)
588{
589  PyObject *osound;
590  int samplerate;
591
592  int length;
593  float tempo;
594  Float32 *samples;
595  PyArrayObject *sound;
596  PyObject *typeObject;
597  int type;
598
599  if(!sizeof(Float32) == sizeof(float))
600    {
601      PyErr_Format(SoundTouchError, 
602                   "numarray Float32 != float on this machine");
603      return NULL;
604    }
605
606  if(!PyArg_ParseTuple(args, "Oi", &osound, &samplerate)) {
607    return NULL;
608  }
609
610  typeObject = PyObject_CallMethod(osound, "type", NULL);
611  type = NA_typeObjectToTypeNo(typeObject);
612  Py_DECREF(typeObject);
613  if(type != tFloat32)
614    {
615      PyErr_Format(SoundTouchError, "sounds must be Float32");
616      return NULL;
617    }
618
619  sound = (PyArrayObject *) NA_InputArray(osound, tFloat32, NUM_C_ARRAY);
620  if(!sound)
621    {
622      Py_XDECREF(sound);
623      PyErr_Format(SoundTouchError, "couldn't convert array to PyArrayObject.");
624      return NULL;
625    }
626  else if(sound->nd != 1)
627    {
628      Py_XDECREF(sound);
629      PyErr_Format(SoundTouchError, "sound arrays must have 1 dimension.");
630      return NULL;
631    }
632
633  samples = (Float32 *) NA_OFFSETDATA(sound);
634  length = sound->dimensions[0];
635
636  /* The real code */
637  int items = 1024;
638  const float *cur = samples;
639  const float *end = samples + length;
640  soundtouch::SAMPLETYPE stage[1024];
641  BPMDetect bpm(2, samplerate);
642
643  while(cur != end)
644    {
645      if(end - cur < items)
646        items = end - cur;
647
648      memcpy(stage, cur, items * sizeof(float));
649      cur += items;
650
651      bpm.inputSamples(stage, items / 2);
652    }
653  tempo = bpm.getBpm();
654
655  Py_DECREF(sound);
656  return Py_BuildValue("f", tempo);
657}
658
659
660static PyMethodDef soundtouch_methods[] = {
661
662  {"find_bpm", soundtouch_find_bpm, METH_VARARGS,
663   "Find the tempo of a chunk of sound. args: sound, samplerate)"},
664  {NULL, NULL, 0, NULL}
665};
666
667
668extern "C" 
669PyMODINIT_FUNC initsoundtouch(void)
670{
671  PyObject *module;
672
673  if(PyType_Ready(&SoundTouchType) < 0)
674    return;
675
676  module = Py_InitModule3("soundtouch", soundtouch_methods,
677                          "soundtouch audio processing library");
678  
679
680  Py_INCREF(&SoundTouchType);
681  PyModule_AddObject(module, "SoundTouch", 
682                     (PyObject *)&SoundTouchType);
683
684  SoundTouchError = PyErr_NewException("soundtouch.error", NULL, NULL);
685  Py_INCREF(SoundTouchError);
686  PyModule_AddObject(module, "error", SoundTouchError);
687
688  import_libnumarray();
689}
690