/external/pysoundtouch14/soundtouchmodule.cpp

http://echo-nest-remix.googlecode.com/ · C++ · 690 lines · 494 code · 140 blank · 56 comment · 37 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. #include <Python.h>
  25. #include <numpy/arrayobject.h>
  26. #include <numpy/libnumarray.h>
  27. #include <iostream>
  28. #include <stdexcept>
  29. #include "libsoundtouch/SoundTouch.h"
  30. #include "libsoundtouch/BPMDetect.h"
  31. static PyObject *SoundTouchError;
  32. #ifndef Py_RETURN_NONE
  33. #define Py_RETURN_NONE Py_INCREF(Py_None); return Py_None;
  34. #endif
  35. /***************************************************************************
  36. * class SoundTouch
  37. ***************************************************************************/
  38. class SoundTouchProxy : public soundtouch::SoundTouch
  39. {
  40. public:
  41. float _getrate(){return rate;}
  42. float _gettempo(){return tempo;}
  43. uint _getchannels(){return channels;}
  44. };
  45. typedef struct {
  46. PyObject_HEAD
  47. SoundTouchProxy *soundtouch;
  48. } SoundTouch;
  49. static void
  50. SoundTouch_dealloc(SoundTouch *self)
  51. {
  52. delete self->soundtouch;
  53. self->ob_type->tp_free((PyObject *) self);
  54. }
  55. static PyObject *
  56. SoundTouch_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
  57. {
  58. SoundTouch *self;
  59. self = (SoundTouch *) type->tp_alloc(type, 0);
  60. self->soundtouch = new SoundTouchProxy;
  61. self->soundtouch->setChannels(2);
  62. self->soundtouch->setSampleRate(44100);
  63. return (PyObject *) self;
  64. }
  65. static int SoundTouch_init(SoundTouch *self, PyObject *args, PyObject *kwds)
  66. {
  67. return 0;
  68. }
  69. static PyObject *
  70. SoundTouch_setRate(SoundTouch *self, PyObject *args)
  71. {
  72. float rate;
  73. if(!PyArg_ParseTuple(args, "f", &rate)) {
  74. return NULL;
  75. }
  76. self->soundtouch->setRate(rate);
  77. Py_RETURN_NONE;
  78. }
  79. static PyObject *
  80. SoundTouch_setTempo(SoundTouch *self, PyObject *args)
  81. {
  82. float tempo;
  83. if(!PyArg_ParseTuple(args, "f", &tempo))
  84. return NULL;
  85. self->soundtouch->setTempo(tempo);
  86. Py_RETURN_NONE;
  87. }
  88. static PyObject *
  89. SoundTouch_setRateChange(SoundTouch *self, PyObject *args)
  90. {
  91. float newRate;
  92. if(!PyArg_ParseTuple(args, "f", &newRate))
  93. return NULL;
  94. self->soundtouch->setRateChange(newRate);
  95. Py_RETURN_NONE;
  96. }
  97. static PyObject *
  98. SoundTouch_setTempoChange(SoundTouch *self, PyObject *args)
  99. {
  100. float newTempo;
  101. if(!PyArg_ParseTuple(args, "f", &newTempo))
  102. return NULL;
  103. self->soundtouch->setTempoChange(newTempo);
  104. Py_RETURN_NONE;
  105. }
  106. static PyObject *
  107. SoundTouch_setPitch(SoundTouch *self, PyObject *args)
  108. {
  109. float pitch;
  110. if(!PyArg_ParseTuple(args, "f", &pitch))
  111. return NULL;
  112. self->soundtouch->setPitch(pitch);
  113. Py_RETURN_NONE;
  114. }
  115. static PyObject *
  116. SoundTouch_setPitchOctaves(SoundTouch *self, PyObject *args)
  117. {
  118. float newPitch;
  119. if(!PyArg_ParseTuple(args, "f", &newPitch))
  120. return NULL;
  121. self->soundtouch->setPitchOctaves(newPitch);
  122. Py_RETURN_NONE;
  123. }
  124. static PyObject *
  125. SoundTouch_setPitchSemiTones(SoundTouch *self, PyObject *args)
  126. {
  127. float newPitch;
  128. if(!PyArg_ParseTuple(args, "f", &newPitch))
  129. return NULL;
  130. self->soundtouch->setPitchSemiTones(newPitch);
  131. Py_RETURN_NONE;
  132. }
  133. static PyObject *
  134. SoundTouch_setChannels(SoundTouch *self, PyObject *args)
  135. {
  136. int channels;
  137. if(!PyArg_ParseTuple(args, "i", &channels))
  138. return NULL;
  139. try
  140. {
  141. self->soundtouch->setChannels(channels);
  142. }
  143. catch(std::runtime_error &error)
  144. {
  145. PyErr_Format(PyExc_RuntimeError, error.what());
  146. return NULL;
  147. }
  148. Py_RETURN_NONE;
  149. }
  150. static PyObject *
  151. SoundTouch_setSampleRate(SoundTouch *self, PyObject *args)
  152. {
  153. int sampleRate;
  154. if(!PyArg_ParseTuple(args, "i", &sampleRate))
  155. return NULL;
  156. self->soundtouch->setSampleRate(sampleRate);
  157. Py_RETURN_NONE;
  158. }
  159. static PyObject *
  160. SoundTouch_flush(SoundTouch *self)
  161. {
  162. self->soundtouch->flush();
  163. Py_RETURN_NONE;
  164. }
  165. static PyObject *
  166. SoundTouch_clear(SoundTouch *self)
  167. {
  168. self->soundtouch->clear();
  169. Py_RETURN_NONE;
  170. }
  171. static PyObject *
  172. SoundTouch_putSamples(SoundTouch *self, PyObject *args)
  173. {
  174. float *samples;
  175. PyObject *osound;
  176. PyArrayObject *sound;
  177. PyObject *typeObject;
  178. int type;
  179. int ret = PyArg_ParseTuple(args, "O", &osound);
  180. if(!ret) {
  181. return NULL;
  182. }
  183. /*
  184. typeObject = PyObject_CallMethod(osound, "type", NULL);
  185. type = NA_typeObjectToTypeNo(typeObject);
  186. Py_DECREF(typeObject);
  187. */
  188. type = tFloat32; // hack for now
  189. if(type != tFloat32)
  190. {
  191. PyErr_Format(SoundTouchError, "sounds must be Float32");
  192. return NULL;
  193. }
  194. sound = (PyArrayObject *) NA_InputArray(osound, tFloat32, NUM_C_ARRAY);
  195. if(!sound)
  196. {
  197. Py_XDECREF(sound);
  198. PyErr_Format(SoundTouchError,
  199. "couldn't convert array to PyArrayObject.");
  200. return NULL;
  201. }
  202. else if(sound->nd != 1)
  203. {
  204. Py_XDECREF(sound);
  205. PyErr_Format(SoundTouchError, "sound arrays must have 1 dimension.");
  206. return NULL;
  207. }
  208. samples = (Float32 *) NA_OFFSETDATA(sound);
  209. uint numSamples = sound->dimensions[0];
  210. uint channels = self->soundtouch->_getchannels();
  211. try
  212. {
  213. self->soundtouch->putSamples(samples, numSamples / channels);
  214. }
  215. catch(std::runtime_error &error)
  216. {
  217. PyErr_Format(PyExc_RuntimeError, error.what());
  218. return NULL;
  219. }
  220. Py_RETURN_NONE;
  221. }
  222. static PyObject *
  223. SoundTouch_receiveSamples(SoundTouch *self, PyObject *args)
  224. {
  225. float *samples;
  226. PyObject *osound;
  227. PyArrayObject *sound;
  228. int type;
  229. PyObject *typeObject;
  230. if(!PyArg_ParseTuple(args, "O", &osound)) {
  231. return NULL;
  232. }
  233. /*typeObject = PyObject_CallMethod(osound, "type", NULL);
  234. type = NA_typeObjectToTypeNo(typeObject);
  235. Py_DECREF(typeObject);
  236. if(type != tFloat32)
  237. {
  238. PyErr_Format(SoundTouchError, "sounds must be Float32");
  239. return NULL;
  240. }
  241. */
  242. type = tFloat32; //hack
  243. sound = (PyArrayObject *) NA_InputArray(osound, tFloat32, NUM_C_ARRAY);
  244. if(!sound)
  245. {
  246. Py_XDECREF(sound);
  247. PyErr_Format(SoundTouchError,
  248. "couldn't convert array to PyArrayObject.");
  249. return NULL;
  250. }
  251. else if(sound->nd != 1)
  252. {
  253. Py_XDECREF(sound);
  254. PyErr_Format(SoundTouchError, "sound arrays must have 1 dimension.");
  255. return NULL;
  256. }
  257. uint length = sound->dimensions[0];
  258. uint channels = self->soundtouch->_getchannels();
  259. samples = (Float32 *) NA_OFFSETDATA(sound);
  260. length = self->soundtouch->receiveSamples(samples, length / channels);
  261. return Py_BuildValue("i", length);
  262. }
  263. static PyObject *
  264. SoundTouch_setSetting(SoundTouch *self, PyObject *args)
  265. {
  266. uint settingId;
  267. uint value;
  268. if(!PyArg_ParseTuple(args, "ii", &settingId, &value))
  269. return NULL;
  270. int ret = self->soundtouch->setSetting(settingId, value) ? 1 : 0;
  271. return Py_BuildValue("b", ret);
  272. }
  273. static PyObject *
  274. SoundTouch_getSetting(SoundTouch *self, PyObject *args)
  275. {
  276. uint settingId;
  277. if(!PyArg_ParseTuple(args, "i", &settingId))
  278. return NULL;
  279. return Py_BuildValue("i", self->soundtouch->getSetting(settingId));
  280. }
  281. static PyObject *
  282. SoundTouch_numUnprocessedSamples(SoundTouch *self)
  283. {
  284. return Py_BuildValue("i", self->soundtouch->numUnprocessedSamples());
  285. }
  286. static PyObject *
  287. SoundTouch_isEmpty(SoundTouch *self)
  288. {
  289. if(self->soundtouch->isEmpty())
  290. {
  291. Py_INCREF(Py_True);
  292. return Py_True;
  293. }
  294. else
  295. {
  296. Py_INCREF(Py_False);
  297. return Py_False;
  298. }
  299. }
  300. static PyObject *
  301. SoundTouch_numSamples(SoundTouch *self)
  302. {
  303. return Py_BuildValue("i", self->soundtouch->numSamples());
  304. }
  305. /*
  306. static PyGetSetDef SoundTouch_getseters[] = {
  307. {"rate", (getter) SoundTouch_getrate, NULL,
  308. "Effective 'rate' value calculated from 'virtualRate', 'virtualTempo'"
  309. "and 'virtualPitch'"},
  310. {
  311. }
  312. */
  313. static PyMethodDef SoundTouch_methods[] = {
  314. {"setRate", (PyCFunction) SoundTouch_setRate, METH_VARARGS,
  315. "Sets new rate control value. Normal rate = 1.0, smaller values"
  316. "represent slower rate, larger faster rates."},
  317. {"setRateChange", (PyCFunction) SoundTouch_setRateChange, METH_VARARGS,
  318. "Sets new rate control value as a difference in percents compared"
  319. "to the original rate (-50 .. +100 %)"},
  320. {"setTempo", (PyCFunction) SoundTouch_setTempo, METH_VARARGS,
  321. "Sets new tempo control value. Normal tempo = 1.0, smaller values"
  322. "represent slower tempo, larger faster tempo."},
  323. {"setTempoChange", (PyCFunction) SoundTouch_setTempoChange, METH_VARARGS,
  324. "Sets new tempo control value as a difference in percents compared"
  325. "to the original tempo (-50 .. +100 %)"},
  326. {"setPitch", (PyCFunction) SoundTouch_setPitch, METH_VARARGS,
  327. "Sets new pitch control value. Original pitch = 1.0, smaller values"
  328. "represent lower pitches, larger values higher pitch."},
  329. {"setPitchOctaves", (PyCFunction) SoundTouch_setPitchOctaves, METH_VARARGS,
  330. "Sets pitch change in octaves compared to the original pitch"
  331. "(-1.00 .. +1.00)"},
  332. {"setPitchSemiTones", (PyCFunction) SoundTouch_setPitchSemiTones, METH_VARARGS,
  333. "Sets pitch change in semi-tones compared to the original pitch"
  334. "(-12 .. +12)"},
  335. {"setChannels", (PyCFunction) SoundTouch_setChannels, METH_VARARGS,
  336. "Sets the number of channels, 1 = mono, 2 = stereo"},
  337. {"setSampleRate", (PyCFunction) SoundTouch_setSampleRate, METH_VARARGS,
  338. "Sets sample rate."},
  339. {"flush", (PyCFunction) SoundTouch_flush, METH_NOARGS,
  340. "Flushes the last samples from the processing pipeline to the output.\n"
  341. "Clears also the internal processing buffers.\n"
  342. "\n"
  343. "Note: This function is meant for extracting the last samples of a sound"
  344. "stream. This function may introduce additional blank samples in the end"
  345. "of the sound stream, and thus it's not recommended to call this function"
  346. "in the middle of a sound stream."},
  347. {"putSamples", (PyCFunction) SoundTouch_putSamples, METH_VARARGS,
  348. "Adds 'numSamples' pcs of samples from the 'samples' memory position into"
  349. "the input of the object. Notice that sample rate _has_to_ be set before"
  350. "calling this function, otherwise throws a runtime_error exception"},
  351. {"receiveSamples", (PyCFunction) SoundTouch_receiveSamples, METH_VARARGS,
  352. "Return samples from beginning of the sample buffer. Copies requested "
  353. "samples to output buffer and removes them from the sample buffer. If "
  354. "there are less than 'numsample' samples in the buffer, returns all "
  355. "that are available."},
  356. {"clear", (PyCFunction) SoundTouch_clear, METH_VARARGS,
  357. "Clears all the samples in the object's output and internal processing"
  358. "buffers."},
  359. {"setSetting", (PyCFunction) SoundTouch_setSetting, METH_VARARGS,
  360. "Changes a setting controlling the processing system behaviour. See the"
  361. "'SETTING_...' defines for available setting ID's.\n"
  362. "\n"
  363. "return 'TRUE' if the setting was succesfully changed."},
  364. {"getSetting", (PyCFunction) SoundTouch_getSetting, METH_VARARGS,
  365. "Reads a setting controlling the processing system behaviour. See the"
  366. "'SETTING_...' defines for available setting ID's.\n"
  367. "\n"
  368. "return the setting value."},
  369. {"numUnprocessedSamples", (PyCFunction) SoundTouch_numUnprocessedSamples, METH_VARARGS,
  370. "Returns number of samples currently unprocessed."},
  371. /***************** FIFOSampleBuffer ***************************************/
  372. {"isEmpty", (PyCFunction) SoundTouch_isEmpty, METH_NOARGS,
  373. "Returns True if the sample buffer is empty."},
  374. {"numSamples", (PyCFunction) SoundTouch_numSamples, METH_NOARGS,
  375. "Returns the number of samples currently in the buffer."},
  376. {NULL} /* Sentinel */
  377. };
  378. static char * SoundTouch_doc =
  379. " SoundTouch - main class for tempo/pitch/rate adjusting routines.\n"
  380. "\n"
  381. " Notes:\n"
  382. " - Initialize the SoundTouch object instance by setting up the sound stream\n"
  383. " parameters with functions 'setSampleRate' and 'setChannels', then set\n"
  384. " desired tempo/pitch/rate settings with the corresponding functions.\n"
  385. "\n"
  386. " - The SoundTouch class behaves like a first-in-first-out pipeline: The\n"
  387. " samples that are to be processed are fed into one of the pipe by calling\n"
  388. " function 'putSamples', while the ready processed samples can be read\n"
  389. " from the other end of the pipeline with function 'receiveSamples'.\n"
  390. "\n"
  391. " - The SoundTouch processing classes require certain sized 'batches' of\n"
  392. " samples in order to process the sound. For this reason the classes buffer\n"
  393. " incoming samples until there are enough of samples available for\n"
  394. " processing, then they carry out the processing step and consequently\n"
  395. " make the processed samples available for outputting.\n"
  396. "\n"
  397. " - For the above reason, the processing routines introduce a certain\n"
  398. " 'latency' between the input and output, so that the samples input to\n"
  399. " SoundTouch may not be immediately available in the output, and neither\n"
  400. " the amount of outputtable samples may not immediately be in direct\n"
  401. " relationship with the amount of previously input samples.\n"
  402. "\n"
  403. " - The tempo/pitch/rate control parameters can be altered during processing.\n"
  404. " Please notice though that they aren't currently protected by semaphores,\n"
  405. " so in multi-thread application external semaphore protection may be\n"
  406. " required.\n"
  407. "\n"
  408. " - This class utilizes classes 'TDStretch' for tempo change (without modifying\n"
  409. " pitch) and 'RateTransposer' for changing the playback rate (that is, both\n"
  410. " tempo and pitch in the same ratio) of the sound. The third available control\n"
  411. " 'pitch' (change pitch but maintain tempo) is produced by a combination of\n"
  412. " combining the two other controls.\n"
  413. "\n"
  414. "Other handy functions that are implemented in the ancestor classes (see\n"
  415. "classes 'FIFOProcessor' and 'FIFOSamplePipe')\n"
  416. "\n"
  417. " - receiveSamples() : Use this function to receive 'ready' processed samples from SoundTouch.\n"
  418. " - numSamples() : Get number of 'ready' samples that can be received with\n"
  419. " function 'receiveSamples()'\n"
  420. " - isEmpty() : Returns nonzero if there aren't any 'ready' samples.\n"
  421. " - clear() : Clears all samples from ready/processing buffers.\n";
  422. static PyTypeObject SoundTouchType = {
  423. PyObject_HEAD_INIT(NULL)
  424. 0, /*ob_size*/
  425. "soundtouch.SoundTouch", /*tp_name*/
  426. sizeof(SoundTouch), /*tp_basicsize*/
  427. 0, /*tp_itemsize*/
  428. (destructor) SoundTouch_dealloc, /*tp_dealloc*/
  429. 0, /*tp_print*/
  430. 0, /*tp_getattr*/
  431. 0, /*tp_setattr*/
  432. 0, /*tp_compare*/
  433. 0, /*tp_repr*/
  434. 0, /*tp_as_number*/
  435. 0, /*tp_as_sequence*/
  436. 0, /*tp_as_mapping*/
  437. 0, /*tp_hash */
  438. 0, /*tp_call*/
  439. 0, /*tp_str*/
  440. 0, /*tp_getattro*/
  441. 0, /*tp_setattro*/
  442. 0, /*tp_as_buffer*/
  443. Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
  444. SoundTouch_doc, /* tp_doc */
  445. 0, /* tp_traverse */
  446. 0, /* tp_clear */
  447. 0, /* tp_richcompare */
  448. 0, /* tp_weaklistoffset */
  449. 0, /* tp_iter */
  450. 0, /* tp_iternext */
  451. SoundTouch_methods, /* tp_methods */
  452. 0, /* tp_members */
  453. 0, /* tp_getset */
  454. 0, /* tp_base */
  455. 0, /* tp_dict */
  456. 0, /* tp_descr_get */
  457. 0, /* tp_descr_set */
  458. 0, /* tp_dictoffset */
  459. (initproc)SoundTouch_init, /* tp_init */
  460. 0, /* tp_alloc */
  461. SoundTouch_new, /* tp_new */
  462. };
  463. /***************************************************************************
  464. * module parts
  465. **************************************************************************/
  466. static PyObject *
  467. soundtouch_find_bpm(PyObject *obj, PyObject *args)
  468. {
  469. PyObject *osound;
  470. int samplerate;
  471. int length;
  472. float tempo;
  473. Float32 *samples;
  474. PyArrayObject *sound;
  475. PyObject *typeObject;
  476. int type;
  477. if(!sizeof(Float32) == sizeof(float))
  478. {
  479. PyErr_Format(SoundTouchError,
  480. "numarray Float32 != float on this machine");
  481. return NULL;
  482. }
  483. if(!PyArg_ParseTuple(args, "Oi", &osound, &samplerate)) {
  484. return NULL;
  485. }
  486. typeObject = PyObject_CallMethod(osound, "type", NULL);
  487. type = NA_typeObjectToTypeNo(typeObject);
  488. Py_DECREF(typeObject);
  489. if(type != tFloat32)
  490. {
  491. PyErr_Format(SoundTouchError, "sounds must be Float32");
  492. return NULL;
  493. }
  494. sound = (PyArrayObject *) NA_InputArray(osound, tFloat32, NUM_C_ARRAY);
  495. if(!sound)
  496. {
  497. Py_XDECREF(sound);
  498. PyErr_Format(SoundTouchError, "couldn't convert array to PyArrayObject.");
  499. return NULL;
  500. }
  501. else if(sound->nd != 1)
  502. {
  503. Py_XDECREF(sound);
  504. PyErr_Format(SoundTouchError, "sound arrays must have 1 dimension.");
  505. return NULL;
  506. }
  507. samples = (Float32 *) NA_OFFSETDATA(sound);
  508. length = sound->dimensions[0];
  509. /* The real code */
  510. int items = 1024;
  511. const float *cur = samples;
  512. const float *end = samples + length;
  513. soundtouch::SAMPLETYPE stage[1024];
  514. BPMDetect bpm(2, samplerate);
  515. while(cur != end)
  516. {
  517. if(end - cur < items)
  518. items = end - cur;
  519. memcpy(stage, cur, items * sizeof(float));
  520. cur += items;
  521. bpm.inputSamples(stage, items / 2);
  522. }
  523. tempo = bpm.getBpm();
  524. Py_DECREF(sound);
  525. return Py_BuildValue("f", tempo);
  526. }
  527. static PyMethodDef soundtouch_methods[] = {
  528. {"find_bpm", soundtouch_find_bpm, METH_VARARGS,
  529. "Find the tempo of a chunk of sound. args: sound, samplerate)"},
  530. {NULL, NULL, 0, NULL}
  531. };
  532. extern "C"
  533. PyMODINIT_FUNC initsoundtouch(void)
  534. {
  535. PyObject *module;
  536. if(PyType_Ready(&SoundTouchType) < 0)
  537. return;
  538. module = Py_InitModule3("soundtouch", soundtouch_methods,
  539. "soundtouch audio processing library");
  540. Py_INCREF(&SoundTouchType);
  541. PyModule_AddObject(module, "SoundTouch",
  542. (PyObject *)&SoundTouchType);
  543. SoundTouchError = PyErr_NewException("soundtouch.error", NULL, NULL);
  544. Py_INCREF(SoundTouchError);
  545. PyModule_AddObject(module, "error", SoundTouchError);
  546. import_libnumarray();
  547. }