You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 lines
3.7KB

  1. #!/usr/bin/python2.5
  2. #
  3. # Copyright 2013 Olivier Gillet.
  4. #
  5. # Author: Olivier Gillet (ol.gillet@gmail.com)
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #
  18. # -----------------------------------------------------------------------------
  19. #
  20. # Streaming .wav file writer.
  21. import copy
  22. import numpy
  23. import struct
  24. _DATA_CHUNK_HEADER_SIZE = 8
  25. _FMT_CHUNK_DATA_SIZE = 16
  26. _FMT_CHUNK_HEADER_SIZE = 8
  27. _RIFF_FORMAT_DESCRIPTOR_SIZE = 4
  28. _UNSIGNED_CHAR_TO_FLOAT_SCALE = 128.0
  29. _FLOAT_TO_UNSIGNED_CHAR_SCALE = 127.0
  30. class AudioStreamWriter(object):
  31. def __init__(self, file_name, sample_rate, bitdepth=16, num_channels=1):
  32. self._sr = sample_rate
  33. self._file_name = file_name
  34. self._sample_rate = sample_rate
  35. self._bitdepth = bitdepth
  36. self._num_channels = num_channels
  37. self._f = file(file_name, 'wb')
  38. self._num_bytes = 0
  39. self._write_header(False)
  40. @staticmethod
  41. def quantize(signal, bitdepth):
  42. """Convert an array of float to an array of integers.
  43. Args:
  44. signal: numpy array. source signal.
  45. bitdepth: int. size of the integer in bits.
  46. Returns:
  47. array of integers.
  48. """
  49. norm = numpy.abs(signal).max()
  50. # Normalization or clipping.
  51. scaled_signal = copy.copy(signal)
  52. if norm > 1.0:
  53. logging.warning('Some samples will be clipped.')
  54. # Clip samples above 1 and below -1.
  55. scaled_signal[scaled_signal < -1] = -1
  56. scaled_signal[scaled_signal > 1] = 1
  57. if bitdepth == 8:
  58. scaled_signal = (scaled_signal + 1.0) * _FLOAT_TO_UNSIGNED_CHAR_SCALE
  59. scaled_signal = numpy.array(scaled_signal, dtype=numpy.uint8)
  60. else:
  61. scale = (1 << (bitdepth - 1)) - 1
  62. # pylint: disable-msg=C6407
  63. scaled_signal = scaled_signal * scale
  64. scaled_signal = numpy.array(scaled_signal, dtype='i%d' % (bitdepth / 8))
  65. return scaled_signal
  66. def _write_header(self, restore_position):
  67. f = self._f
  68. total_size = _RIFF_FORMAT_DESCRIPTOR_SIZE
  69. total_size += _FMT_CHUNK_HEADER_SIZE + _FMT_CHUNK_DATA_SIZE
  70. total_size += _DATA_CHUNK_HEADER_SIZE + self._num_bytes
  71. current_position = f.tell()
  72. f.seek(0)
  73. f.write('RIFF')
  74. f.write(struct.pack('<L', total_size))
  75. f.write('WAVEfmt ')
  76. bitrate = self._sample_rate * self._num_channels * (self._bitdepth / 8)
  77. bits_per_sample = self._num_channels * (self._bitdepth / 8)
  78. f.write(struct.pack(
  79. '<LHHLLHH',
  80. 16,
  81. 1,
  82. self._num_channels,
  83. self._sample_rate,
  84. bitrate,
  85. bits_per_sample,
  86. self._bitdepth))
  87. f.write('data')
  88. f.write(struct.pack('<L', self._num_bytes))
  89. if restore_position:
  90. f.seek(current_position)
  91. def append(self, signal):
  92. if signal.dtype == numpy.uint8 or signal.dtype == numpy.int16:
  93. assert self._bitdepth == signal.dtype.itemsize * 8
  94. scaled_signal = signal
  95. else:
  96. scaled_signal = self.quantize(signal, self._bitdepth)
  97. if scaled_signal.ndim == 1:
  98. assert self._num_channels == 1
  99. else:
  100. assert self._num_channels == scaled_signal.shape[1]
  101. scaled_signal.tofile(self._f)
  102. self._num_bytes += scaled_signal.nbytes
  103. self._write_header(True)