Last active
April 6, 2021 16:11
-
-
Save daveisadork/4717535 to your computer and use it in GitHub Desktop.
Functions to convert between ReplayGain and SoundCheck.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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