def __find_next_mp3_frame__(cls, mp3file): from audiotools.id3 import skip_id3v2_comment # if we're starting at an ID3v2 header, skip it to save a bunch of time bytes_skipped = skip_id3v2_comment(mp3file) # then find the next mp3 frame from audiotools.bitstream import BitstreamReader reader = BitstreamReader(mp3file, False) pos = reader.getpos() try: (sync, mpeg_id, layer_description) = reader.parse("11u 2u 2u 1p") except IOError as err: raise err while (not ((sync == 0x7FF) and (mpeg_id in (0, 2, 3)) and (layer_description in (1, 2, 3)))): reader.setpos(pos) reader.skip(8) bytes_skipped += 1 pos = reader.getpos() try: (sync, mpeg_id, layer_description) = reader.parse("11u 2u 2u 1p") except IOError as err: raise err else: reader.setpos(pos) return bytes_skipped
def __find_mp3_start__(cls, mp3file): """places mp3file at the position of the MP3 file's start""" from audiotools.id3 import skip_id3v2_comment # if we're starting at an ID3v2 header, skip it to save a bunch of time skip_id3v2_comment(mp3file) from audiotools.bitstream import BitstreamReader reader = BitstreamReader(mp3file, False) # skip over any bytes that aren't a valid MPEG header pos = reader.getpos() (frame_sync, mpeg_id, layer) = reader.parse("11u 2u 2u 1p") while (not ((frame_sync == 0x7FF) and (mpeg_id in (0, 2, 3)) and (layer in (1, 2, 3)))): reader.setpos(pos) reader.skip(8) pos = reader.getpos() reader.setpos(pos)
class WaveReader(object): """a PCMReader object for reading wave file contents""" def __init__(self, wave_filename): """wave_filename is a string""" from audiotools.bitstream import BitstreamReader self.stream = BitstreamReader(open(wave_filename, "rb"), True) # ensure RIFF<size>WAVE header is ok try: (riff, total_size, wave) = self.stream.parse("4b 32u 4b") except struct.error: from audiotools.text import ERR_WAV_INVALID_WAVE self.stream.close() raise ValueError(ERR_WAV_INVALID_WAVE) if riff != b'RIFF': from audiotools.text import ERR_WAV_NOT_WAVE self.stream.close() raise ValueError(ERR_WAV_NOT_WAVE) elif wave != b'WAVE': from audiotools.text import ERR_WAV_INVALID_WAVE self.stream.close() raise ValueError(ERR_WAV_INVALID_WAVE) else: total_size -= 4 fmt_chunk_read = False # walk through chunks until "data" chunk encountered while total_size > 0: try: (chunk_id, chunk_size) = self.stream.parse("4b 32u") except struct.error: from audiotools.text import ERR_WAV_INVALID_WAVE self.stream.close() raise ValueError(ERR_WAV_INVALID_WAVE) if not frozenset(chunk_id).issubset(WaveAudio.PRINTABLE_ASCII): from audiotools.text import ERR_WAV_INVALID_CHUNK self.stream.close() raise ValueError(ERR_WAV_INVALID_CHUNK) else: total_size -= 8 if chunk_id == b"fmt ": # when "fmt " chunk encountered, # use it to populate PCMReader attributes (self.channels, self.sample_rate, self.bits_per_sample, channel_mask) = parse_fmt(self.stream) self.channel_mask = int(channel_mask) self.bytes_per_pcm_frame = ((self.bits_per_sample // 8) * self.channels) fmt_chunk_read = True elif chunk_id == b"data": # when "data" chunk encountered, # use its size to determine total PCM frames # and ready PCMReader for reading if not fmt_chunk_read: from audiotools.text import ERR_WAV_PREMATURE_DATA self.stream.close() raise ValueError(ERR_WAV_PREMATURE_DATA) else: self.total_pcm_frames = (chunk_size // self.bytes_per_pcm_frame) self.remaining_pcm_frames = self.total_pcm_frames self.data_start = self.stream.getpos() return else: # all other chunks are ignored self.stream.skip_bytes(chunk_size) if chunk_size % 2: if len(self.stream.read_bytes(1)) < 1: from audiotools.text import ERR_WAV_INVALID_CHUNK self.stream.close() raise ValueError(ERR_WAV_INVALID_CHUNK) total_size -= (chunk_size + 1) else: total_size -= chunk_size else: # raise an error if no "data" chunk is encountered from audiotools.text import ERR_WAV_NO_DATA_CHUNK self.stream.close() raise ValueError(ERR_WAV_NO_DATA_CHUNK) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def read(self, pcm_frames): """try to read a pcm.FrameList with the given number of PCM frames""" # try to read requested PCM frames or remaining frames requested_pcm_frames = min(max(pcm_frames, 1), self.remaining_pcm_frames) requested_bytes = (self.bytes_per_pcm_frame * requested_pcm_frames) pcm_data = self.stream.read_bytes(requested_bytes) # raise exception if "data" chunk exhausted early if len(pcm_data) < requested_bytes: from audiotools.text import ERR_WAV_TRUNCATED_DATA_CHUNK raise IOError(ERR_WAV_TRUNCATED_DATA_CHUNK) else: self.remaining_pcm_frames -= requested_pcm_frames # return parsed chunk return FrameList(pcm_data, self.channels, self.bits_per_sample, False, self.bits_per_sample != 8) def read_closed(self, pcm_frames): raise ValueError("cannot read closed stream") def seek(self, pcm_frame_offset): """tries to seek to the given PCM frame offset returns the total amount of frames actually seeked over""" if pcm_frame_offset < 0: from audiotools.text import ERR_NEGATIVE_SEEK raise ValueError(ERR_NEGATIVE_SEEK) # ensure one doesn't walk off the end of the file pcm_frame_offset = min(pcm_frame_offset, self.total_pcm_frames) # position file in "data" chunk self.stream.setpos(self.data_start) self.stream.seek(pcm_frame_offset * self.bytes_per_pcm_frame, 1) self.remaining_pcm_frames = (self.total_pcm_frames - pcm_frame_offset) return pcm_frame_offset def seek_closed(self, pcm_frame_offset): raise ValueError("cannot seek closed stream") def close(self): """closes the stream for reading""" self.stream.close() self.read = self.read_closed self.seek = self.seek_closed
class AiffReader(object): """a PCMReader object for reading AIFF file contents""" def __init__(self, aiff_filename): """aiff_filename is a string""" from audiotools.bitstream import BitstreamReader self.stream = BitstreamReader(open(aiff_filename, "rb"), False) # ensure FORM<size>AIFF header is ok try: (form, total_size, aiff) = self.stream.parse("4b 32u 4b") except struct.error: from audiotools.text import ERR_AIFF_INVALID_AIFF raise InvalidAIFF(ERR_AIFF_INVALID_AIFF) if form != b'FORM': from audiotools.text import ERR_AIFF_NOT_AIFF raise ValueError(ERR_AIFF_NOT_AIFF) elif aiff != b'AIFF': from audiotools.text import ERR_AIFF_INVALID_AIFF raise ValueError(ERR_AIFF_INVALID_AIFF) else: total_size -= 4 comm_chunk_read = False # walk through chunks until "SSND" chunk encountered while total_size > 0: try: (chunk_id, chunk_size) = self.stream.parse("4b 32u") except struct.error: from audiotools.text import ERR_AIFF_INVALID_AIFF raise ValueError(ERR_AIFF_INVALID_AIFF) if not frozenset(chunk_id).issubset(AiffAudio.PRINTABLE_ASCII): from audiotools.text import ERR_AIFF_INVALID_CHUNK_ID raise ValueError(ERR_AIFF_INVALID_CHUNK_ID) else: total_size -= 8 if chunk_id == b"COMM": # when "COMM" chunk encountered, # use it to populate PCMReader attributes (self.channels, self.total_pcm_frames, self.bits_per_sample, self.sample_rate, channel_mask) = parse_comm(self.stream) self.channel_mask = int(channel_mask) self.bytes_per_pcm_frame = ((self.bits_per_sample // 8) * self.channels) self.remaining_pcm_frames = self.total_pcm_frames comm_chunk_read = True elif chunk_id == b"SSND": # when "SSND" chunk encountered, # strip off the "offset" and "block_size" attributes # and ready PCMReader for reading if not comm_chunk_read: from audiotools.text import ERR_AIFF_PREMATURE_SSND_CHUNK raise ValueError(ERR_AIFF_PREMATURE_SSND_CHUNK) else: self.stream.skip_bytes(8) self.ssnd_start = self.stream.getpos() return else: # all other chunks are ignored self.stream.skip_bytes(chunk_size) if chunk_size % 2: if len(self.stream.read_bytes(1)) < 1: from audiotools.text import ERR_AIFF_INVALID_CHUNK raise ValueError(ERR_AIFF_INVALID_CHUNK) total_size -= (chunk_size + 1) else: total_size -= chunk_size else: # raise an error if no "SSND" chunk is encountered from audiotools.text import ERR_AIFF_NO_SSND_CHUNK raise ValueError(ERR_AIFF_NO_SSND_CHUNK) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def read(self, pcm_frames): """try to read a pcm.FrameList with the given number of PCM frames""" # try to read requested PCM frames or remaining frames requested_pcm_frames = min(max(pcm_frames, 1), self.remaining_pcm_frames) requested_bytes = (self.bytes_per_pcm_frame * requested_pcm_frames) pcm_data = self.stream.read_bytes(requested_bytes) # raise exception if "SSND" chunk exhausted early if len(pcm_data) < requested_bytes: from audiotools.text import ERR_AIFF_TRUNCATED_SSND_CHUNK raise IOError(ERR_AIFF_TRUNCATED_SSND_CHUNK) else: self.remaining_pcm_frames -= requested_pcm_frames # return parsed chunk return FrameList(pcm_data, self.channels, self.bits_per_sample, True, True) def read_closed(self, pcm_frames): raise ValueError("cannot read closed stream") def seek(self, pcm_frame_offset): """tries to seek to the given PCM frame offset returns the total amount of frames actually seeked over""" if pcm_frame_offset < 0: from audiotools.text import ERR_NEGATIVE_SEEK raise ValueError(ERR_NEGATIVE_SEEK) # ensure one doesn't walk off the end of the file pcm_frame_offset = min(pcm_frame_offset, self.total_pcm_frames) # position file in "SSND" chunk self.stream.setpos(self.ssnd_start) self.stream.seek((pcm_frame_offset * self.bytes_per_pcm_frame), 1) self.remaining_pcm_frames = (self.total_pcm_frames - pcm_frame_offset) return pcm_frame_offset def seek_closed(self, pcm_frame_offset): raise ValueError("cannot seek closed stream") def close(self): """closes the stream for reading""" self.stream.close() self.read = self.read_closed self.seek = self.seek_closed
class SHNDecoder(object): def __init__(self, filename): self.reader = BitstreamReader(open(filename, "rb"), False) (self.file_type, self.channels, self.block_length, self.max_LPC, self.number_of_means) = self.read_header() if (1 <= self.file_type) and (self.file_type <= 2): self.bits_per_sample = 8 self.signed_samples = (self.file_type == 1) elif (3 <= self.file_type) and (self.file_type <= 6): self.bits_per_sample = 16 self.signed_samples = (self.file_type in (3, 5)) else: raise ValueError("unsupported Shorten file type") self.wrapped_samples = [[0] * 3 for c in range(self.channels)] self.means = [[0] * self.number_of_means for c in range(self.channels)] self.left_shift = 0 self.stream_finished = False # try to read the first command for a wave/aiff header data_start = self.reader.getpos() self.read_metadata() self.reader.setpos(data_start) def read_metadata(self): from io import BytesIO command = self.unsigned(2) if command == 9: # got verbatim, so read data verbatim_bytes = ints_to_bytes([self.unsigned(8) & 0xFF for i in range(self.unsigned(5))]) try: wave = BitstreamReader(BytesIO(verbatim_bytes), True) header = wave.read_bytes(12) if header.startswith(b"RIFF") and header.endswith(b"WAVE"): # got RIFF/WAVE header, so parse wave blocks as needed total_size = len(verbatim_bytes) - 12 while total_size > 0: (chunk_id, chunk_size) = wave.parse("4b 32u") total_size -= 8 if chunk_id == b'fmt ': (channels, self.sample_rate, bits_per_sample, channel_mask) = parse_fmt( wave.substream(chunk_size)) self.channel_mask = int(channel_mask) return else: if chunk_size % 2: wave.read_bytes(chunk_size + 1) total_size -= (chunk_size + 1) else: wave.read_bytes(chunk_size) total_size -= chunk_size else: # no fmt chunk, so use default metadata pass except (IOError, ValueError): pass try: aiff = BitstreamReader(BytesIO(verbatim_bytes), False) header = aiff.read_bytes(12) if header.startswith(b"FORM") and header.endswith(b"AIFF"): # got FORM/AIFF header, so parse aiff blocks as needed total_size = len(verbatim_bytes) - 12 while total_size > 0: (chunk_id, chunk_size) = aiff.parse("4b 32u") total_size -= 8 if chunk_id == b'COMM': (channels, total_sample_frames, bits_per_sample, self.sample_rate, channel_mask) = parse_comm( aiff.substream(chunk_size)) self.channel_mask = int(channel_mask) return else: if chunk_size % 2: aiff.read_bytes(chunk_size + 1) total_size -= (chunk_size + 1) else: aiff.read_bytes(chunk_size) total_size -= chunk_size else: # no COMM chunk, so use default metadata pass except IOError: pass # got something else, so invent some PCM parameters self.sample_rate = 44100 if self.channels == 1: self.channel_mask = 0x4 elif self.channels == 2: self.channel_mask = 0x3 else: self.channel_mask = 0 def unsigned(self, c): MSB = self.reader.unary(1) LSB = self.reader.read(c) return MSB * 2 ** c + LSB def signed(self, c): u = self.unsigned(c + 1) if (u % 2) == 0: return u // 2 else: return -(u // 2) - 1 def long(self): return self.unsigned(self.unsigned(2)) def skip_unsigned(self, c): self.reader.skip_unary(1) self.reader.skip(c) def read_header(self): magic = self.reader.read_bytes(4) if magic != b"ajkg": raise ValueError("invalid magic number") version = self.reader.read(8) if version != 2: raise ValueError("unsupported version") file_type = self.long() channels = self.long() block_length = self.long() max_LPC = self.long() number_of_means = self.long() bytes_to_skip = self.long() self.reader.read_bytes(bytes_to_skip) return (file_type, channels, block_length, max_LPC, number_of_means) def read(self, pcm_frames): if self.stream_finished: return from_channels([empty_framelist(1, self.bits_per_sample) for channel in range(self.channels)]) c = 0 samples = [] unshifted = [] while True: command = self.unsigned(2) if (((0 <= command) and (command <= 3) or (7 <= command) and (command <= 8))): # audio data commands if command == 0: # DIFF0 samples.append(self.read_diff0(self.block_length, self.means[c])) elif command == 1: # DIFF1 samples.append(self.read_diff1(self.block_length, self.wrapped_samples[c])) elif command == 2: # DIFF2 samples.append(self.read_diff2(self.block_length, self.wrapped_samples[c])) elif command == 3: # DIFF3 samples.append(self.read_diff3(self.block_length, self.wrapped_samples[c])) elif command == 7: # QLPC samples.append(self.read_qlpc(self.block_length, self.means[c], self.wrapped_samples[c])) elif command == 8: # ZERO samples.append([0] * self.block_length) # update means for channel self.means[c].append(shnmean(samples[c])) self.means[c] = self.means[c][1:] # wrap samples for next command in channel self.wrapped_samples[c] = samples[c][-(max(3, self.max_LPC)):] # apply left shift to samples if self.left_shift > 0: unshifted.append([s << self.left_shift for s in samples[c]]) else: unshifted.append(samples[c]) c += 1 if c == self.channels: # return a FrameList from shifted data return from_channels([from_list(channel, 1, self.bits_per_sample, self.signed_samples) for channel in unshifted]) else: # non audio commands if command == 4: # QUIT self.stream_finished = True return from_channels( [empty_framelist(1, self.bits_per_sample) for channel in range(self.channels)]) elif command == 5: # BLOCKSIZE self.block_length = self.long() elif command == 6: # BITSHIFT self.left_shift = self.unsigned(2) elif command == 9: # VERBATIM # skip this command during reading size = self.unsigned(5) for i in range(size): self.skip_unsigned(8) else: raise ValueError("unsupported Shorten command") def read_diff0(self, block_length, means): offset = shnmean(means) energy = self.unsigned(3) samples = [] for i in range(block_length): residual = self.signed(energy) samples.append(residual + offset) return samples def read_diff1(self, block_length, previous_samples): samples = previous_samples[-1:] energy = self.unsigned(3) for i in range(1, block_length + 1): residual = self.signed(energy) samples.append(samples[i - 1] + residual) return samples[1:] def read_diff2(self, block_length, previous_samples): samples = previous_samples[-2:] energy = self.unsigned(3) for i in range(2, block_length + 2): residual = self.signed(energy) samples.append((2 * samples[i - 1]) - samples[i - 2] + residual) return samples[2:] def read_diff3(self, block_length, previous_samples): samples = previous_samples[-3:] energy = self.unsigned(3) for i in range(3, block_length + 3): residual = self.signed(energy) samples.append((3 * (samples[i - 1] - samples[i - 2])) + samples[i - 3] + residual) return samples[3:] def read_qlpc(self, block_length, means, previous_samples): offset = shnmean(means) energy = self.unsigned(3) LPC_count = self.unsigned(2) LPC_coeff = [self.signed(5) for i in range(LPC_count)] unoffset = [] samples = previous_samples[-LPC_count:] for i in range(block_length): residual = self.signed(energy) LPC_sum = 2 ** 5 for j in range(LPC_count): if (i - j - 1) < 0: # apply offset to warm-up samples LPC_sum += (LPC_coeff[j] * (samples[LPC_count + (i - j - 1)] - offset)) else: LPC_sum += LPC_coeff[j] * unoffset[i - j - 1] unoffset.append(LPC_sum // 2 ** 5 + residual) return [u + offset for u in unoffset] def close(self): self.reader.close() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close()
class SHNDecoder(object): def __init__(self, filename): self.reader = BitstreamReader(open(filename, "rb"), False) (self.file_type, self.channels, self.block_length, self.max_LPC, self.number_of_means) = self.read_header() if (1 <= self.file_type) and (self.file_type <= 2): self.bits_per_sample = 8 self.signed_samples = (self.file_type == 1) elif (3 <= self.file_type) and (self.file_type <= 6): self.bits_per_sample = 16 self.signed_samples = (self.file_type in (3, 5)) else: raise ValueError("unsupported Shorten file type") self.wrapped_samples = [[0] * 3 for c in range(self.channels)] self.means = [[0] * self.number_of_means for c in range(self.channels)] self.left_shift = 0 self.stream_finished = False # try to read the first command for a wave/aiff header data_start = self.reader.getpos() self.read_metadata() self.reader.setpos(data_start) def read_metadata(self): from io import BytesIO command = self.unsigned(2) if command == 9: # got verbatim, so read data verbatim_bytes = ints_to_bytes( [self.unsigned(8) & 0xFF for i in range(self.unsigned(5))]) try: wave = BitstreamReader(BytesIO(verbatim_bytes), True) header = wave.read_bytes(12) if header.startswith(b"RIFF") and header.endswith(b"WAVE"): # got RIFF/WAVE header, so parse wave blocks as needed total_size = len(verbatim_bytes) - 12 while total_size > 0: (chunk_id, chunk_size) = wave.parse("4b 32u") total_size -= 8 if chunk_id == b'fmt ': (channels, self.sample_rate, bits_per_sample, channel_mask) = parse_fmt( wave.substream(chunk_size)) self.channel_mask = int(channel_mask) return else: if chunk_size % 2: wave.read_bytes(chunk_size + 1) total_size -= (chunk_size + 1) else: wave.read_bytes(chunk_size) total_size -= chunk_size else: # no fmt chunk, so use default metadata pass except (IOError, ValueError): pass try: aiff = BitstreamReader(BytesIO(verbatim_bytes), False) header = aiff.read_bytes(12) if header.startswith(b"FORM") and header.endswith(b"AIFF"): # got FORM/AIFF header, so parse aiff blocks as needed total_size = len(verbatim_bytes) - 12 while total_size > 0: (chunk_id, chunk_size) = aiff.parse("4b 32u") total_size -= 8 if chunk_id == b'COMM': (channels, total_sample_frames, bits_per_sample, self.sample_rate, channel_mask) = parse_comm( aiff.substream(chunk_size)) self.channel_mask = int(channel_mask) return else: if chunk_size % 2: aiff.read_bytes(chunk_size + 1) total_size -= (chunk_size + 1) else: aiff.read_bytes(chunk_size) total_size -= chunk_size else: # no COMM chunk, so use default metadata pass except IOError: pass # got something else, so invent some PCM parameters self.sample_rate = 44100 if self.channels == 1: self.channel_mask = 0x4 elif self.channels == 2: self.channel_mask = 0x3 else: self.channel_mask = 0 def unsigned(self, c): MSB = self.reader.unary(1) LSB = self.reader.read(c) return MSB * 2**c + LSB def signed(self, c): u = self.unsigned(c + 1) if (u % 2) == 0: return u // 2 else: return -(u // 2) - 1 def long(self): return self.unsigned(self.unsigned(2)) def skip_unsigned(self, c): self.reader.skip_unary(1) self.reader.skip(c) def read_header(self): magic = self.reader.read_bytes(4) if magic != b"ajkg": raise ValueError("invalid magic number") version = self.reader.read(8) if version != 2: raise ValueError("unsupported version") file_type = self.long() channels = self.long() block_length = self.long() max_LPC = self.long() number_of_means = self.long() bytes_to_skip = self.long() self.reader.read_bytes(bytes_to_skip) return (file_type, channels, block_length, max_LPC, number_of_means) def read(self, pcm_frames): if self.stream_finished: return from_channels([ empty_framelist(1, self.bits_per_sample) for channel in range(self.channels) ]) c = 0 samples = [] unshifted = [] while True: command = self.unsigned(2) if (((0 <= command) and (command <= 3) or (7 <= command) and (command <= 8))): # audio data commands if command == 0: # DIFF0 samples.append( self.read_diff0(self.block_length, self.means[c])) elif command == 1: # DIFF1 samples.append( self.read_diff1(self.block_length, self.wrapped_samples[c])) elif command == 2: # DIFF2 samples.append( self.read_diff2(self.block_length, self.wrapped_samples[c])) elif command == 3: # DIFF3 samples.append( self.read_diff3(self.block_length, self.wrapped_samples[c])) elif command == 7: # QLPC samples.append( self.read_qlpc(self.block_length, self.means[c], self.wrapped_samples[c])) elif command == 8: # ZERO samples.append([0] * self.block_length) # update means for channel self.means[c].append(shnmean(samples[c])) self.means[c] = self.means[c][1:] # wrap samples for next command in channel self.wrapped_samples[c] = samples[c][-(max(3, self.max_LPC)):] # apply left shift to samples if self.left_shift > 0: unshifted.append( [s << self.left_shift for s in samples[c]]) else: unshifted.append(samples[c]) c += 1 if c == self.channels: # return a FrameList from shifted data return from_channels([ from_list(channel, 1, self.bits_per_sample, self.signed_samples) for channel in unshifted ]) else: # non audio commands if command == 4: # QUIT self.stream_finished = True return from_channels([ empty_framelist(1, self.bits_per_sample) for channel in range(self.channels) ]) elif command == 5: # BLOCKSIZE self.block_length = self.long() elif command == 6: # BITSHIFT self.left_shift = self.unsigned(2) elif command == 9: # VERBATIM # skip this command during reading size = self.unsigned(5) for i in range(size): self.skip_unsigned(8) else: raise ValueError("unsupported Shorten command") def read_diff0(self, block_length, means): offset = shnmean(means) energy = self.unsigned(3) samples = [] for i in range(block_length): residual = self.signed(energy) samples.append(residual + offset) return samples def read_diff1(self, block_length, previous_samples): samples = previous_samples[-1:] energy = self.unsigned(3) for i in range(1, block_length + 1): residual = self.signed(energy) samples.append(samples[i - 1] + residual) return samples[1:] def read_diff2(self, block_length, previous_samples): samples = previous_samples[-2:] energy = self.unsigned(3) for i in range(2, block_length + 2): residual = self.signed(energy) samples.append((2 * samples[i - 1]) - samples[i - 2] + residual) return samples[2:] def read_diff3(self, block_length, previous_samples): samples = previous_samples[-3:] energy = self.unsigned(3) for i in range(3, block_length + 3): residual = self.signed(energy) samples.append((3 * (samples[i - 1] - samples[i - 2])) + samples[i - 3] + residual) return samples[3:] def read_qlpc(self, block_length, means, previous_samples): offset = shnmean(means) energy = self.unsigned(3) LPC_count = self.unsigned(2) LPC_coeff = [self.signed(5) for i in range(LPC_count)] unoffset = [] samples = previous_samples[-LPC_count:] for i in range(block_length): residual = self.signed(energy) LPC_sum = 2**5 for j in range(LPC_count): if (i - j - 1) < 0: # apply offset to warm-up samples LPC_sum += (LPC_coeff[j] * (samples[LPC_count + (i - j - 1)] - offset)) else: LPC_sum += LPC_coeff[j] * unoffset[i - j - 1] unoffset.append(LPC_sum // 2**5 + residual) return [u + offset for u in unoffset] def close(self): self.reader.close() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close()
class WavPackDecoder(object): def __init__(self, filename): self.reader = BitstreamReader(open(filename, "rb"), True) stream_start = self.reader.getpos() # read initial block to populate # sample_rate, bits_per_sample, channels, and channel_mask block_header = Block_Header.read(self.reader) sub_blocks_size = block_header.block_size - 24 sub_blocks_data = self.reader.substream(sub_blocks_size) if block_header.sample_rate != 15: self.sample_rate = [6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000][block_header.sample_rate] else: sub_blocks_start = sub_blocks_data.getpos() try: for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size): if (((sub_block.metadata_function == 7) and (sub_block.nondecoder_data == 1))): self.sample_rate = sub_block.data.read( sub_block.data_size() * 8) break else: raise ValueError("invalid sample rate") finally: sub_blocks_data.setpos(sub_blocks_start) self.bits_per_sample = \ [8, 16, 24, 32][block_header.bits_per_sample] if block_header.initial_block and block_header.final_block: if (((block_header.mono_output == 0) or (block_header.false_stereo == 1))): self.channels = 2 self.channel_mask = 0x3 else: self.channels = 1 self.channel_mask = 0x4 else: # look for channel mask sub block sub_blocks_start = sub_blocks_data.getpos() try: for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size): if (((sub_block.metadata_function == 13) and (sub_block.nondecoder_data == 0))): self.channels = sub_block.data.read(8) self.channel_mask = sub_block.data.read( (sub_block.data_size() - 1) * 8) break else: # FIXME - handle case of no channel mask sub block raise NotImplementedError() finally: sub_blocks_data.setpos(sub_blocks_start) self.reader.setpos(stream_start) self.pcm_finished = False self.md5_checked = False self.md5sum = md5() def read(self, pcm_frames): if self.pcm_finished: if not self.md5_checked: block_start = self.reader.getpos() try: try: header = Block_Header.read(self.reader) sub_blocks_size = header.block_size - 24 sub_blocks_data = \ self.reader.substream(sub_blocks_size) for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size): if (((sub_block.metadata_function == 6) and (sub_block.nondecoder_data == 1))): if ((sub_block.data.read_bytes(16) != self.md5sum.digest())): raise ValueError("invalid stream MD5 sum") except (IOError, ValueError): # no error if a block isn't found pass finally: self.reader.setpos(block_start) return empty_framelist(self.channels, self.bits_per_sample) channels = [] while True: # in place of a do-while loop try: block_header = Block_Header.read(self.reader) except (ValueError, IOError): self.pcm_finished = True return empty_framelist(self.channels, self.bits_per_sample) sub_blocks_size = block_header.block_size - 24 sub_blocks_data = self.reader.substream(sub_blocks_size) channels.extend(read_block(block_header, sub_blocks_size, sub_blocks_data)) if block_header.final_block == 1: break if ((block_header.block_index + block_header.block_samples) >= block_header.total_samples): self.pcm_finished = True # combine channels of audio data into single block block = from_channels([from_list(ch, 1, self.bits_per_sample, True) for ch in channels]) # update MD5 sum self.md5sum.update(block.to_bytes(False, self.bits_per_sample > 8)) # return single block of audio data return block def close(self): self.reader.close() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close()
class WavPackDecoder(object): def __init__(self, filename): self.reader = BitstreamReader(open(filename, "rb"), True) stream_start = self.reader.getpos() # read initial block to populate # sample_rate, bits_per_sample, channels, and channel_mask block_header = Block_Header.read(self.reader) sub_blocks_size = block_header.block_size - 24 sub_blocks_data = self.reader.substream(sub_blocks_size) if block_header.sample_rate != 15: self.sample_rate = [ 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 ][block_header.sample_rate] else: sub_blocks_start = sub_blocks_data.getpos() try: for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size): if (((sub_block.metadata_function == 7) and (sub_block.nondecoder_data == 1))): self.sample_rate = sub_block.data.read( sub_block.data_size() * 8) break else: raise ValueError("invalid sample rate") finally: sub_blocks_data.setpos(sub_blocks_start) self.bits_per_sample = \ [8, 16, 24, 32][block_header.bits_per_sample] if block_header.initial_block and block_header.final_block: if (((block_header.mono_output == 0) or (block_header.false_stereo == 1))): self.channels = 2 self.channel_mask = 0x3 else: self.channels = 1 self.channel_mask = 0x4 else: # look for channel mask sub block sub_blocks_start = sub_blocks_data.getpos() try: for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size): if (((sub_block.metadata_function == 13) and (sub_block.nondecoder_data == 0))): self.channels = sub_block.data.read(8) self.channel_mask = sub_block.data.read( (sub_block.data_size() - 1) * 8) break else: # FIXME - handle case of no channel mask sub block raise NotImplementedError() finally: sub_blocks_data.setpos(sub_blocks_start) self.reader.setpos(stream_start) self.pcm_finished = False self.md5_checked = False self.md5sum = md5() def read(self, pcm_frames): if self.pcm_finished: if not self.md5_checked: block_start = self.reader.getpos() try: try: header = Block_Header.read(self.reader) sub_blocks_size = header.block_size - 24 sub_blocks_data = \ self.reader.substream(sub_blocks_size) for sub_block in sub_blocks(sub_blocks_data, sub_blocks_size): if (((sub_block.metadata_function == 6) and (sub_block.nondecoder_data == 1))): if ((sub_block.data.read_bytes(16) != self.md5sum.digest())): raise ValueError("invalid stream MD5 sum") except (IOError, ValueError): # no error if a block isn't found pass finally: self.reader.setpos(block_start) return empty_framelist(self.channels, self.bits_per_sample) channels = [] while True: # in place of a do-while loop try: block_header = Block_Header.read(self.reader) except (ValueError, IOError): self.pcm_finished = True return empty_framelist(self.channels, self.bits_per_sample) sub_blocks_size = block_header.block_size - 24 sub_blocks_data = self.reader.substream(sub_blocks_size) channels.extend( read_block(block_header, sub_blocks_size, sub_blocks_data)) if block_header.final_block == 1: break if ((block_header.block_index + block_header.block_samples) >= block_header.total_samples): self.pcm_finished = True # combine channels of audio data into single block block = from_channels( [from_list(ch, 1, self.bits_per_sample, True) for ch in channels]) # update MD5 sum self.md5sum.update(block.to_bytes(False, self.bits_per_sample > 8)) # return single block of audio data return block def close(self): self.reader.close() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close()
class ALACDecoder(object): def __init__(self, filename): self.reader = BitstreamReader(open(filename, "rb"), False) stream_start = self.reader.getpos() # locate the "alac" atom # which is full of required decoding parameters try: stsd = self.find_sub_atom(b"moov", b"trak", b"mdia", b"minf", b"stbl", b"stsd") except KeyError: raise ValueError("required stsd atom not found") (stsd_version, descriptions) = stsd.parse("8u 24p 32u") (alac1, alac2, self.samples_per_frame, self.bits_per_sample, self.history_multiplier, self.initial_history, self.maximum_k, self.channels, self.sample_rate) = stsd.parse( # ignore much of the stuff in the "high" ALAC atom "32p 4b 6P 16p 16p 16p 4P 16p 16p 16p 16p 4P" + # and use the attributes in the "low" ALAC atom instead "32p 4b 4P 32u 8p 8u 8u 8u 8u 8u 16p 32p 32p 32u") self.channel_mask = {1: 0x0004, 2: 0x0003, 3: 0x0007, 4: 0x0107, 5: 0x0037, 6: 0x003F, 7: 0x013F, 8: 0x00FF}.get(self.channels, 0) if (alac1 != b'alac') or (alac2 != b'alac'): raise ValueError("Invalid alac atom") # also locate the "mdhd" atom # which contains the stream's length in PCM frames self.reader.setpos(stream_start) mdhd = self.find_sub_atom(b"moov", b"trak", b"mdia", b"mdhd") (version, ) = mdhd.parse("8u 24p") if version == 0: (self.total_pcm_frames,) = mdhd.parse( "32p 32p 32p 32u 2P 16p") elif version == 1: (self.total_pcm_frames,) = mdhd.parse( "64p 64p 32p 64U 2P 16p") else: raise ValueError("invalid mdhd version") # finally, set our stream to the "mdat" atom self.reader.setpos(stream_start) (atom_size, atom_name) = self.reader.parse("32u 4b") while atom_name != b"mdat": self.reader.skip_bytes(atom_size - 8) (atom_size, atom_name) = self.reader.parse("32u 4b") def find_sub_atom(self, *atom_names): reader = self.reader for (last, next_atom) in iter_last(iter(atom_names)): try: (length, stream_atom) = reader.parse("32u 4b") while stream_atom != next_atom: reader.skip_bytes(length - 8) (length, stream_atom) = reader.parse("32u 4b") if last: return reader.substream(length - 8) else: reader = reader.substream(length - 8) except IOError: raise KeyError(next_atom) def read(self, pcm_frames): # if the stream is exhausted, return an empty pcm.FrameList object if self.total_pcm_frames == 0: return empty_framelist(self.channels, self.bits_per_sample) # otherwise, read one ALAC frameset's worth of frame data frameset_data = [] frame_channels = self.reader.read(3) + 1 while frame_channels != 0x8: frameset_data.extend(self.read_frame(frame_channels)) frame_channels = self.reader.read(3) + 1 self.reader.byte_align() # reorder the frameset to Wave order, depending on channel count if (self.channels == 1) or (self.channels == 2): pass elif self.channels == 3: frameset_data = [frameset_data[1], frameset_data[2], frameset_data[0]] elif self.channels == 4: frameset_data = [frameset_data[1], frameset_data[2], frameset_data[0], frameset_data[3]] elif self.channels == 5: frameset_data = [frameset_data[1], frameset_data[2], frameset_data[0], frameset_data[3], frameset_data[4]] elif self.channels == 6: frameset_data = [frameset_data[1], frameset_data[2], frameset_data[0], frameset_data[5], frameset_data[3], frameset_data[4]] elif self.channels == 7: frameset_data = [frameset_data[1], frameset_data[2], frameset_data[0], frameset_data[6], frameset_data[3], frameset_data[4], frameset_data[5]] elif self.channels == 8: frameset_data = [frameset_data[3], frameset_data[4], frameset_data[0], frameset_data[7], frameset_data[5], frameset_data[6], frameset_data[1], frameset_data[2]] else: raise ValueError("unsupported channel count") framelist = from_channels([from_list(channel, 1, self.bits_per_sample, True) for channel in frameset_data]) # deduct PCM frames from remainder self.total_pcm_frames -= framelist.frames # return samples as a pcm.FrameList object return framelist def read_frame(self, channel_count): """returns a list of PCM sample lists, one per channel""" # read the ALAC frame header self.reader.skip(16) has_sample_count = self.reader.read(1) uncompressed_lsb_size = self.reader.read(2) uncompressed = self.reader.read(1) if has_sample_count: sample_count = self.reader.read(32) else: sample_count = self.samples_per_frame if uncompressed == 1: # if the frame is uncompressed, # read the raw, interlaced samples samples = [self.reader.read_signed(self.bits_per_sample) for i in range(sample_count * channel_count)] return [samples[i::channel_count] for i in range(channel_count)] else: # if the frame is compressed, # read the interlacing parameters interlacing_shift = self.reader.read(8) interlacing_leftweight = self.reader.read(8) # subframe headers subframe_headers = [self.read_subframe_header() for i in range(channel_count)] # optional uncompressed LSB values if uncompressed_lsb_size > 0: uncompressed_lsbs = [ self.reader.read(uncompressed_lsb_size * 8) for i in range(sample_count * channel_count)] else: uncompressed_lsbs = [] sample_size = (self.bits_per_sample - (uncompressed_lsb_size * 8) + channel_count - 1) # and residual blocks residual_blocks = [self.read_residuals(sample_size, sample_count) for i in range(channel_count)] # calculate subframe samples based on # subframe header's QLP coefficients and QLP shift-needed decoded_subframes = [self.decode_subframe(header[0], header[1], sample_size, residuals) for (header, residuals) in zip(subframe_headers, residual_blocks)] # decorrelate channels according interlacing shift and leftweight decorrelated_channels = self.decorrelate_channels( decoded_subframes, interlacing_shift, interlacing_leftweight) # if uncompressed LSB values are present, # prepend them to each sample of each channel if uncompressed_lsb_size > 0: channels = [] for (i, channel) in enumerate(decorrelated_channels): assert(len(channel) == len(uncompressed_lsbs[i::channel_count])) channels.append([s << (uncompressed_lsb_size * 8) | l for (s, l) in zip(channel, uncompressed_lsbs[i::channel_count])]) return channels else: return decorrelated_channels def read_subframe_header(self): prediction_type = self.reader.read(4) qlp_shift_needed = self.reader.read(4) rice_modifier = self.reader.read(3) qlp_coefficients = [self.reader.read_signed(16) for i in range(self.reader.read(5))] return (qlp_shift_needed, qlp_coefficients) def read_residuals(self, sample_size, sample_count): residuals = [] history = self.initial_history sign_modifier = 0 i = 0 while i < sample_count: # get an unsigned residual based on "history" # and on "sample_size" as a lst resort k = min(log2(history // (2 ** 9) + 3), self.maximum_k) unsigned = self.read_residual(k, sample_size) + sign_modifier # clear out old sign modifier, if any sign_modifier = 0 # change unsigned residual to signed residual if unsigned & 1: residuals.append(-((unsigned + 1) // 2)) else: residuals.append(unsigned // 2) # update history based on unsigned residual if unsigned <= 0xFFFF: history += ((unsigned * self.history_multiplier) - ((history * self.history_multiplier) >> 9)) else: history = 0xFFFF # if history gets too small, we may have a block of 0 samples # which can be compressed more efficiently if (history < 128) and ((i + 1) < sample_count): zeroes_k = min(7 - log2(history) + ((history + 16) // 64), self.maximum_k) zero_residuals = self.read_residual(zeroes_k, 16) if zero_residuals > 0: residuals.extend([0] * zero_residuals) i += zero_residuals history = 0 if zero_residuals <= 0xFFFF: sign_modifier = 1 i += 1 return residuals def read_residual(self, k, sample_size): msb = self.reader.read_huffman_code(RESIDUAL) if msb == -1: return self.reader.read(sample_size) elif k == 0: return msb else: lsb = self.reader.read(k) if lsb > 1: return msb * ((1 << k) - 1) + (lsb - 1) elif lsb == 1: self.reader.unread(1) return msb * ((1 << k) - 1) else: self.reader.unread(0) return msb * ((1 << k) - 1) def decode_subframe(self, qlp_shift_needed, qlp_coefficients, sample_size, residuals): # first sample is always copied verbatim samples = [residuals.pop(0)] if len(qlp_coefficients) < 31: # the next "coefficient count" samples # are applied as differences to the previous for i in range(len(qlp_coefficients)): samples.append(truncate_bits(samples[-1] + residuals.pop(0), sample_size)) # remaining samples are processed much like LPC for residual in residuals: base_sample = samples[-len(qlp_coefficients) - 1] lpc_sum = sum([(s - base_sample) * c for (s, c) in zip(samples[-len(qlp_coefficients):], reversed(qlp_coefficients))]) outval = (1 << (qlp_shift_needed - 1)) + lpc_sum outval >>= qlp_shift_needed samples.append(truncate_bits(outval + residual + base_sample, sample_size)) buf = samples[-len(qlp_coefficients) - 2:-1] # error value then adjusts the coefficients table if residual > 0: predictor_num = len(qlp_coefficients) - 1 while (predictor_num >= 0) and residual > 0: val = (buf[0] - buf[len(qlp_coefficients) - predictor_num]) sign = sign_only(val) qlp_coefficients[predictor_num] -= sign val *= sign residual -= ((val >> qlp_shift_needed) * (len(qlp_coefficients) - predictor_num)) predictor_num -= 1 elif residual < 0: # the same as above, but we break if residual goes positive predictor_num = len(qlp_coefficients) - 1 while (predictor_num >= 0) and (residual < 0): val = (buf[0] - buf[len(qlp_coefficients) - predictor_num]) sign = -sign_only(val) qlp_coefficients[predictor_num] -= sign val *= sign residual -= ((val >> qlp_shift_needed) * (len(qlp_coefficients) - predictor_num)) predictor_num -= 1 else: # residuals are encoded as simple difference values for residual in residuals: samples.append(truncate_bits(samples[-1] + residual, sample_size)) return samples def decorrelate_channels(self, channel_data, interlacing_shift, interlacing_leftweight): if len(channel_data) != 2: return channel_data elif interlacing_leftweight == 0: return channel_data else: left = [] right = [] for (ch1, ch2) in zip(*channel_data): right.append(ch1 - ((ch2 * interlacing_leftweight) // (2 ** interlacing_shift))) left.append(ch2 + right[-1]) return [left, right] def close(self): self.reader.close() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close()