from math import modf #fuck me rounding is hard # header map # https://wiki.multimedia.cx/index.php/CRI_ADX_file MAGIC_OFF = 0x00 MAGIC_LEN = 2 MAGIC = 0x8000 DATA_OFF_OFF = 0x02 DATA_OFF_LEN = 2 DATA_OFF_MIN = 0x38 FORMAT_OFF = 0x04 FORMAT_LEN = 1 FORMAT = 3 # always 3 for adx apparently LOOP_TYPE_OFF = 0x12 LOOP_TYPE_LEN = 1 LOOP_TYPE_SUP = [4] # supported loop styles SRATE_OFF = 0x08 SRATE_LEN = 4 SCOUNT_OFF = 0x0c SCOUNT_LEN = 4 CRYPT_OFF = 0x13 CRYPT_LEN = 1 LOOP4_FLAG_OFF = 0x24 LOOP4_FLAG_LEN = 4 LOOP4_START_OFF = 0x28 LOOP4_START_LEN = 4 LOOP4_END_OFF = 0x30 LOOP4_END_LEN = 4 #class independent functions def dumb_round(number): dec = modf(number)[0] if (dec <= 0.5): return int(number) else: return int(number+1) class adx_file(object): magic = 0 form = 0 l_style = 0 encrypted = 0 sam_rate = 0 sam_count = 0 l_flag = 0 l_start = 0 l_end = 0 l_exists = 0 d_offset = 0 is_valid = 0 data = None def __init__(self, data): # first, validate self.data = data self.magic = self.get_val(MAGIC_OFF, MAGIC_LEN) self.form = self.get_val(FORMAT_OFF, FORMAT_LEN) self.l_style = self.get_val(LOOP_TYPE_OFF, LOOP_TYPE_LEN) self.encrypted = self.get_val(CRYPT_OFF, CRYPT_LEN) self.d_offset = self.get_val(DATA_OFF_OFF, DATA_OFF_LEN) if self.d_offset < DATA_OFF_MIN: return None self.l_exists = 1 # now load things for math self.sam_rate = self.get_val(SRATE_OFF, SRATE_LEN) self.sam_count = self.get_val(SCOUNT_OFF, SCOUNT_LEN) self.validate() def get_val(self, offset, length): return int.from_bytes(self.data[offset:offset+length], byteorder='big', signed=False) def validate(self): if self.magic != MAGIC: raise ValueError("Invalid ADX File") if self.form != FORMAT: raise ValueError("Invalid ADX File") if self.encrypted: raise NotImplementedError("Encryption Not Supported") self.is_valid = 1 def parse_loop_data(self): if adxf.l_style == 4: adxf.l_flag = adxf.get_val(LOOP4_FLAG_OFF, LOOP4_FLAG_LEN) adxf.l_start = adxf.get_val(LOOP4_START_OFF, LOOP4_START_LEN) adxf.l_end = adxf.get_val(LOOP4_END_OFF, LOOP4_END_LEN) if not adxf.l_flag: return None if adxf.l_start == 0 and adxf.l_end == adxf.sam_count: return None if adxf.l_start > adxf.sam_count or adxf.l_end > adxf.sam_count: raise ValueError("Invalid Loop Data") return None if not adxf.l_style in LOOP_TYPE_SUP: raise NotImplementedError("Loop Style {} Not Implemented".format(str(adxf.l_style))) return None ret = {} ret['duration'] = adxf.sam_count / adxf.sam_rate ret['loop_start'] = {} ret['loop_start']['seconds'] = adxf.l_start / adxf.sam_rate ret['loop_start']['samples_native'] = adxf.l_start ret['loop_start']['samples_48k'] = dumb_round(adxf.l_start / adxf.sam_rate * 48000) ret['loop_end'] = {} ret['loop_end']['seconds'] = adxf.l_end / adxf.sam_rate ret['loop_end']['samples_native'] = adxf.l_end ret['loop_end']['samples_48k'] = dumb_round(adxf.l_end / adxf.sam_rate * 48000) return ret