def encode(input_filename): """Given a filename, read the bytes of the file and encode it into an array. The array (of type SAMPLE_TYPE) is returned. """ input_file = open(input_filename, mode='rb') input_bytes = input_file.read() notes = [] for byte in input_bytes: all_bits = [] # Append each byte of the input, left to right, into the bit list. for i in range(8): if byte & (1 << (7 - i)) > 0: all_bits.append(1) else: all_bits.append(0) notes.append(all_bits) csn = 0 # the value of the chord seqence number all_chords = [] # a list of concatenated bits n = 0 # our position in the notes list while n < len(notes): chord = [] # We write the nibbles of the control note first. The # chord sequence number goes in the high nibble. ctrl_note = ~csn << 4 ctrl_nib = 0 if n == 0: # We are looking at the first byte of the input file, so we # will include the START nibble in the control note. ctrl_nib = chords.CTRL_NIB_START elif n >= len(notes) - (chords.CHORD_SIZE - 1): # We are looking at one of the last bytes of the input # file, which means we will be writing the last chord. Therefore # we include the END nibble in the control note. ctrl_nib = chords.CTRL_NIB_END else: # We are writing a regular data chord, somewhere between the START # and END chords. ctrl_nib = chords.CTRL_NIB_DATA # The control nibble goes in the low nibble of the control note. ctrl_note |= ctrl_nib # Append the bits of the control note (left to right). chord += bits.to_bit_list(ctrl_note) # Increment the sequence number for the next chord. csn += 1 # We will now append the bits of CHORD_SIZE - 1 more notes # to the chord, as long as there are that many bytes available # in the input file. i = 0 while n < len(notes) and i < chords.CHORD_SIZE - 1: chord += notes[n] n += 1 i += 1 # If there are not enough notes to fill this chord, pad it # with zero notes. (This means that we ran out of bytes from # the input file while constructing the last chord.) while len(chord) % (chords.CHORD_SIZE * 8) != 0: chord += [0] * 8 all_chords.append(chord) freq_map = bands.get_frequency_map() data = array.array(wavetools.SAMPLE_TYPE) # Play each chord into the WAVE buffer. The bits of each chord are played # from low frequencies to high frequencies. for c in all_chords: num_samples = chords.CHORD_LENGTH for s in range(num_samples): sample = 0 for f in range(len(freq_map)): if c[f] > 0: sample += (wavetools.MAX_AMPLITUDE / len(freq_map)) * \ sin(2 * pi * freq_map[f] * \ s / wavetools.SAMPLE_RATE) data.append(int(sample)) return data
def decode_chord(samples): """Given a list of samples, interpret the samples as one chord and return a tuple (ctrl, bytes) where ctrl is the control note byte in the chord and bytes is a list of bytes of the remaining data notes. """ map = bands.get_frequency_map() spec = fourier.fft(samples) # Compute amplitudes for each sample in window. amps = [] for i in range(len(spec)): amps.append(sqrt(pow(spec[i].real, 2) + pow(spec[i].imag, 2))) # Determine whether the amplitudes indicate 0 or 1. j = 0 chord = [] inRange = False foundBit = False # For all indices under the Nyquist Limit. for i in range(len(amps) // 2): # While the frequency is within some range around # a frequency in the frequency map. while abs(map[j] - i * wavetools.SAMPLE_RATE / \ fourier.WINDOW_SIZE) <= FREQ_RANGE: inRange = True # If one of the amplitudes within this frequency range # is above a threshold, then the bit should be a 1. if amps[i] > AMP_THRESHOLD: foundBit = True break i += 1 if inRange: chord.append(1 if foundBit else 0) inRange = False foundBit = False j += 1 if j == len(map): break # From our list of bits in the chord, construct bytes by slicing the # bit list in byte-sized sublists and convert them to bytes. notes = [] for n in range(chords.CHORD_SIZE): note_start = (n * 8) note_end = note_start + 8 note_bits = chord[note_start:note_end] notes.append(bits.to_byte(note_bits)) # The first note is the control note, so we return it separately # from the other notes. return (notes[0], notes[1:])