Skip to content

Instantly share code, notes, and snippets.

@daveisadork
Last active April 6, 2021 16:11
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daveisadork/4717535 to your computer and use it in GitHub Desktop.
Save daveisadork/4717535 to your computer and use it in GitHub Desktop.
Functions to convert between ReplayGain and SoundCheck.
import math
import struct
# The following is from http://id3.org/iTunes%20Normalization%20settings
# The iTunNORM tag consists of 5 value pairs. These 10 values are encoded as
# ASCII Hex values of 8 characters each inside the tag (plus a space as
# prefix).
# The tag can be found in MP3, AIFF, AAC and Apple Lossless files.
# The relevant information is what is encoded in these 5 value pairs. The
# first value of each pair is for the left audio channel, the second value of
# each pair is for the right channel.
# 0/1: Volume adjustment in milliWatt/dBm
# 2/3: Same as 0/1, but not based on 1/1000 Watt but 1/2500 Watt
# 4/5: Not sure, but always the same values for songs that only differs in
# volume - so maybe some statistical values.
# 6/7: The peak value (maximum sample) as absolute (positive) value;
# therefore up to 32768 (for songs using 16-Bit samples).
# 8/9: Not sure, same as for 4/5: same values for songs that only differs in
# volume.
# iTunes is choosing the maximum value of the both first pairs (of the first
# 4 values) to adjust the whole song.
def sc2rg(soundcheck):
"""Convert a SoundCheck tag to ReplayGain values"""
# SoundCheck tags consist of 10 numbers, each represented by 8 characters
# of ASCII hex preceded by a space.
try:
soundcheck = soundcheck.replace(' ', '').decode('hex')
soundcheck = struct.unpack('!iiiiiiiiii', soundcheck)
except:
# SoundCheck isn't in the format we expect, so return default values
return 0.0, 0.0
# SoundCheck stores absolute calculated/measured RMS value in an unknown
# unit. We need to find the ratio of this measurement compared to a
# reference value of 1000 to get our gain in dB. We play it safe by using
# the larger of the two values (i.e., the most attenuation).
gain = math.log10((max(*soundcheck[:2]) or 1000) / 1000.0) * -10
# SoundCheck stores peak values as the actual value of the sample, and
# again separately for the left and right channels. We need to convert
# this to a percentage of full scale, which is 32768 for a 16 bit sample.
# Once again, we play it safe by using the larger of the two values.
peak = max(soundcheck[6:8]) / 32768.0
return round(gain, 2), round(peak, 6)
def rg2sc(gain, peak):
"""Convert ReplayGain values to a SoundCheck tag"""
if not isinstance(gain, float):
gain = float(gain.lower().strip(' db'))
# SoundCheck stores the peak value as the actual value of the sample,
# rather than the percentage of full scale that RG uses, so we do
# a simple conversion assuming 16 bit samples.
peak = float(peak) * 32768.0
# SoundCheck stores absolute RMS values in some unknown units rather than
# the dB values RG uses. We can calculate these absolute values from the
# gain ratio using a reference value of 1000 units. We also enforce the
# maximum value here, which is equivalent to about -18.2dB.
g1 = min(round((10 ** (gain / -10)) * 1000), 65534)
# Same as above, except our reference level is 2500 units.
g2 = min(round((10 ** (gain / -10)) * 2500), 65534)
# The purpose of these values are unknown, but they also seem to be
# unused so we just use 0
uk = 0
values = (g1, g1, g2, g2, uk, uk, peak, peak, uk, uk)
soundcheck = (u' %08X' * 10) % values
return soundcheck
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment