Class: Shazamio::DecodedMessage
- Inherits:
-
Object
- Object
- Shazamio::DecodedMessage
- Defined in:
- lib/shazamio/signature.rb
Overview
Shazam's raw binary "signature" format: a 48-byte header (magic numbers, CRC32, sample count) followed by a TLV list of frequency-band peaks. Ported field-for-field from signature.py's RawSignatureHeader / DecodedMessage, replacing ctypes.Structure with Array#pack/#unpack.
Constant Summary collapse
- HEADER_FORMAT =
12 x uint32 little-endian = 48 bytes
"V12"- MAGIC1 =
0xCAFE2580- MAGIC2 =
0x94119C00
Instance Attribute Summary collapse
-
#frequency_band_to_sound_peaks ⇒ Object
Returns the value of attribute frequency_band_to_sound_peaks.
-
#number_samples ⇒ Object
Returns the value of attribute number_samples.
-
#sample_rate_hz ⇒ Object
Returns the value of attribute sample_rate_hz.
Class Method Summary collapse
Instance Method Summary collapse
- #encode_to_binary ⇒ Object
- #encode_to_uri ⇒ Object
-
#initialize ⇒ DecodedMessage
constructor
A new instance of DecodedMessage.
Constructor Details
#initialize ⇒ DecodedMessage
Returns a new instance of DecodedMessage.
47 48 49 |
# File 'lib/shazamio/signature.rb', line 47 def initialize @frequency_band_to_sound_peaks = {} end |
Instance Attribute Details
#frequency_band_to_sound_peaks ⇒ Object
Returns the value of attribute frequency_band_to_sound_peaks.
45 46 47 |
# File 'lib/shazamio/signature.rb', line 45 def frequency_band_to_sound_peaks @frequency_band_to_sound_peaks end |
#number_samples ⇒ Object
Returns the value of attribute number_samples.
45 46 47 |
# File 'lib/shazamio/signature.rb', line 45 def number_samples @number_samples end |
#sample_rate_hz ⇒ Object
Returns the value of attribute sample_rate_hz.
45 46 47 |
# File 'lib/shazamio/signature.rb', line 45 def sample_rate_hz @sample_rate_hz end |
Class Method Details
.decode_from_binary(data) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/shazamio/signature.rb', line 51 def self.decode_from_binary(data) msg = new buf = StringIO.new(data) buf.binmode buf.seek(8) check_summable_data = buf.read buf.seek(0) header_bytes = buf.read(48) fields = header_bytes.unpack(HEADER_FORMAT) magic1, crc32, size_minus_header, magic2, _v1a, _v1b, _v1c, shifted_sample_rate_id, _v2a, _v2b, number_samples_plus_divided_sample_rate, _fixed_value = fields raise "bad magic1" unless magic1 == MAGIC1 raise "bad size" unless size_minus_header == data.bytesize - 48 raise "bad crc32" unless (Zlib.crc32(check_summable_data) & 0xFFFFFFFF) == crc32 raise "bad magic2" unless magic2 == MAGIC2 msg.sample_rate_hz = SampleRate.hz_for(shifted_sample_rate_id >> 27) msg.number_samples = (number_samples_plus_divided_sample_rate - msg.sample_rate_hz * 0.24).to_i raise "bad tlv preamble" unless buf.read(4).unpack1("V") == 0x40000000 raise "bad tlv size" unless buf.read(4).unpack1("V") == data.bytesize - 48 msg.frequency_band_to_sound_peaks = {} until buf.eof? tlv_header = buf.read(8) break if tlv_header.nil? || tlv_header.empty? frequency_band_id, frequency_peaks_size = tlv_header.unpack("VV") padding = (-frequency_peaks_size) % 4 peaks_buf = StringIO.new(buf.read(frequency_peaks_size)) buf.read(padding) band = frequency_band_id - 0x60030040 fft_pass_number = 0 msg.frequency_band_to_sound_peaks[band] = [] until peaks_buf.eof? raw_offset = peaks_buf.read(1) break if raw_offset.nil? || raw_offset.empty? offset = raw_offset.unpack1("C") if offset == 0xFF fft_pass_number = peaks_buf.read(4).unpack1("V") next else fft_pass_number += offset end peak_magnitude = peaks_buf.read(2).unpack1("v") corrected_peak_frequency_bin = peaks_buf.read(2).unpack1("v") msg.frequency_band_to_sound_peaks[band] << FrequencyPeak.new( fft_pass_number, peak_magnitude, corrected_peak_frequency_bin, msg.sample_rate_hz ) end end msg end |
Instance Method Details
#encode_to_binary ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/shazamio/signature.rb', line 116 def encode_to_binary contents_buf = StringIO.new contents_buf.binmode frequency_band_to_sound_peaks.sort.each do |frequency_band, frequency_peaks| peaks_buf = StringIO.new peaks_buf.binmode fft_pass_number = 0 frequency_peaks.each do |peak| raise "peaks must be sorted by fft_pass_number" if peak.fft_pass_number < fft_pass_number if peak.fft_pass_number - fft_pass_number >= 255 peaks_buf.write([0xFF].pack("C")) peaks_buf.write([peak.fft_pass_number].pack("V")) fft_pass_number = peak.fft_pass_number end peaks_buf.write([peak.fft_pass_number - fft_pass_number].pack("C")) peaks_buf.write([peak.peak_magnitude].pack("v")) peaks_buf.write([peak.corrected_peak_frequency_bin].pack("v")) fft_pass_number = peak.fft_pass_number end peaks_bytes = peaks_buf.string contents_buf.write([0x60030040 + frequency_band].pack("V")) contents_buf.write([peaks_bytes.bytesize].pack("V")) contents_buf.write(peaks_bytes) contents_buf.write("\x00" * ((-peaks_bytes.bytesize) % 4)) end contents = contents_buf.string size_minus_header = contents.bytesize + 8 shifted_sample_rate_id = SampleRate.value_for_hz(sample_rate_hz) << 27 fixed_value = (15 << 19) + 0x40000 number_samples_plus_divided_sample_rate = (number_samples + sample_rate_hz * 0.24).to_i # First pass: header with crc32 = 0, to compute the real crc32 over it. header_fields = [ MAGIC1, 0, size_minus_header, MAGIC2, 0, 0, 0, shifted_sample_rate_id, 0, 0, number_samples_plus_divided_sample_rate, fixed_value ] body = StringIO.new body.binmode body.write([0x40000000].pack("V")) body.write([contents.bytesize + 8].pack("V")) body.write(contents) crc = Zlib.crc32(header_fields.pack(HEADER_FORMAT)[8..] + body.string) & 0xFFFFFFFF header_fields[1] = crc header_fields.pack(HEADER_FORMAT) + body.string end |
#encode_to_uri ⇒ Object
176 177 178 |
# File 'lib/shazamio/signature.rb', line 176 def encode_to_uri DATA_URI_PREFIX + Base64.strict_encode64(encode_to_binary) end |