/src/echonest/modify.py

http://echo-nest-remix.googlecode.com/ · Python · 109 lines · 88 code · 11 blank · 10 comment · 36 complexity · d9e865ed7e3a1c4ae0ece1ddceb215ea MD5 · raw file

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. """
  4. modify.py
  5. Created by Ben Lacker on 2009-06-12.
  6. """
  7. from echonest.audio import *
  8. import numpy
  9. import soundtouch
  10. class Modify(soundtouch.SoundTouch):
  11. def __init__(self, sampleRate=44100, numChannels=1, blockSize = 10000):
  12. self.setSampleRate(sampleRate)
  13. self.setChannels(numChannels)
  14. self.sampleRate = sampleRate
  15. self.numChannels = numChannels
  16. self.blockSize = blockSize
  17. def doInBlocks(self, f, in_data, arg):
  18. # For now, make everything mono. We'll deal with channels later
  19. if in_data.ndim > 1:
  20. in_data = in_data[:, 0]
  21. collect = []
  22. if len(in_data) > self.blockSize:
  23. for x in range(len(in_data)/self.blockSize):
  24. start = x * self.blockSize
  25. data = in_data[start:start + self.blockSize -1]
  26. collect.append(self.processAudio(f, data, arg))
  27. data = in_data[-1*(len(in_data) % self.blockSize):]
  28. collect.append(self.processAudio(f, data, arg))
  29. else:
  30. collect.append(self.processAudio(f, in_data, arg))
  31. return assemble(collect, numChannels=self.numChannels, sampleRate=self.sampleRate)
  32. def processAudio(self, f, data, arg):
  33. f(arg)
  34. self.putSamples(data)
  35. out_data = numpy.array(numpy.zeros((len(data)*2,), dtype=numpy.float32))
  36. out_samples = self.receiveSamples(out_data)
  37. new_ad = AudioData(ndarray=out_data[:out_samples], shape=(out_samples, ),
  38. sampleRate=self.sampleRate, numChannels=self.numChannels)
  39. return new_ad
  40. def shiftRate(self, audio_data, ratio=1):
  41. if not isinstance(audio_data, AudioData):
  42. raise TypeError('First argument must be an AudioData object.')
  43. if not (isinstance(ratio, int) or isinstance(ratio, float)):
  44. raise ValueError('Ratio must be an int or float.')
  45. if (ratio < 0) or (ratio > 10):
  46. raise ValueError('Ratio must be between 0 and 10.')
  47. return self.doInBlocks(self.setRate, audio_data.data, ratio)
  48. def shiftTempo(self, audio_data, ratio):
  49. if not isinstance(audio_data, AudioData):
  50. raise TypeError('First argument must be an AudioData object.')
  51. if not (isinstance(ratio, int) or isinstance(ratio, float)):
  52. raise ValueError('Ratio must be an int or float.')
  53. if (ratio < 0) or (ratio > 10):
  54. raise ValueError('Ratio must be between 0 and 10.')
  55. return self.doInBlocks(self.setTempo, audio_data.data, ratio)
  56. def shiftRateChange(self, audio_data, percent):
  57. if not isinstance(audio_data, AudioData):
  58. raise TypeError('First argument must be an AudioData object.')
  59. if not (isinstance(percent, int) or isinstance(percent, float)):
  60. raise ValueError('Percent must be an int or float.')
  61. if (percent < -50) or (percent > 100):
  62. raise ValueError('Percent must be between -50 and 100.')
  63. return self.doInBlocks(self.setRateChange, audio_data.data, percent)
  64. def shiftTempoChange(self, audio_data, percent):
  65. if not isinstance(audio_data, AudioData):
  66. raise TypeError('First argument must be an AudioData object.')
  67. if not (isinstance(percent, int) or isinstance(percent, float)):
  68. raise ValueError('Percent must be an int or float.')
  69. if (percent < -50) or (percent > 100):
  70. raise ValueError('Percent must be between -50 and 100.')
  71. return self.doInBlocks(self.setTempoChange, audio_data.data, percent)
  72. def shiftPitchSemiTones(self, audio_data, semitones=0):
  73. if not isinstance(audio_data, AudioData):
  74. raise TypeError('First argument must be an AudioData object.')
  75. if not isinstance(semitones, int):
  76. raise TypeError('Second argument must be an integer.')
  77. # I think this is right, but maybe it has to be between -12 and 12?
  78. if abs(semitones) > 60:
  79. raise ValueError('Semitones argument must be an int between -60 and 60.')
  80. return self.doInBlocks(self.setPitchSemiTones, audio_data.data, semitones)
  81. def shiftPitchOctaves(self, audio_data, octaves=0):
  82. if not isinstance(audio_data, AudioData):
  83. raise TypeError('First argument must be an AudioData object.')
  84. if not (isinstance(octaves, int) or isinstance(octaves, float)):
  85. raise ValueError('Octaves must be an int or float.')
  86. if abs(octaves) > 5:
  87. raise ValueError('Octaves argument must be between -5 and 5.')
  88. # what are the limits? Nothing in soundtouch documentation...
  89. return self.doInBlocks(self.setPitchOctaves, audio_data.data, octaves)
  90. def shiftPitch(self, audio_data, ratio=1):
  91. if not isinstance(audio_data, AudioData):
  92. raise TypeError('First argument must be an AudioData object.')
  93. if not (isinstance(ratio, int) or isinstance(ratio, float)):
  94. raise ValueError('Ratio must be an int or float.')
  95. if (ratio < 0) or (ratio > 10):
  96. raise ValueError('Ratio must be between 0 and 10.')
  97. return self.doInBlocks(self.setPitch, audio_data.data, ratio)