Guest User

Untitled

a guest
Nov 21st, 2017
72
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 2.98 KB | None | 0 0
  1. #!/usr/bin/env python2
  2. '''
  3. Short script to make OPL music (in DOSBox DRO files) less loud.
  4. '''
  5. from struct import *
  6. from collections import namedtuple
  7. import sys
  8. import argparse
  9.  
  10. def namedunpack(data, names, format):
  11. '''
  12. Unpacks into a named tuple.
  13. '''
  14. str = namedtuple('struct', names)
  15. data = str(*unpack(format, data))
  16. return data
  17.  
  18. CARRIERS = (0x43, 0x44, 0x45, 0x4B, 0x4C, 0x4D, 0x53, 0x54, 0x55)
  19. LEVEL_TO_ALGORITHM = {
  20. 0x40: 0xC0, 0x41: 0xC0, 0x42: 0xC1, 0x43: 0xC1, 0x44: 0xC2, 0x45: 0xC2,
  21. 0x48: 0xC3, 0x49: 0xC3, 0x4A: 0xC4, 0x4B: 0xC4, 0x4C: 0xC5, 0x4D: 0xC5,
  22. 0x50: 0xC6, 0x51: 0xC6, 0x52: 0xC7, 0x53: 0xC7, 0x54: 0xC8, 0x55: 0xC8
  23. }
  24.  
  25. class OPL2Quieter(object):
  26.  
  27. def __init__(self, quiet_function):
  28. self.quiet_function = quiet_function
  29. self.registers = [0x00 for i in xrange(0x100)]
  30.  
  31. def write(self, register, value):
  32. if 0x40 <= register <= 0x55: # if it is a level register
  33. # algorithm bit is the LSB of algorithm/feedback reg
  34. algorithm = self.registers[LEVEL_TO_ALGORITHM[register]] & 0x01
  35.  
  36. if algorithm == 0x00:
  37. # FM synthesis, only modify the level if it's a carrier
  38. modify_level = True if register in CARRIERS else False
  39. else:
  40. # AM synthesis, modify level for both operators
  41. modify_level = True
  42.  
  43. if modify_level:
  44. # keep the top two bits (the KSL value) separate
  45. # for purposes of calculation
  46. ksl = value & 0xC0
  47. # perform the volume modification on the lower six bits
  48. level = self.quiet_function(value & 0x3F)
  49. value = ksl | (level & 0x3F)
  50.  
  51. self.registers[register] = value
  52. return register, value
  53.  
  54. parser = argparse.ArgumentParser(
  55. description='Reduces the volume of a DOSBox DRO file.'
  56. )
  57. parser.add_argument('infile', help='input file', type=str)
  58. parser.add_argument('outfile', help='output file', type=str)
  59. args = parser.parse_args()
  60.  
  61. infile = open(args.infile, 'rb')
  62. outfile = open(args.outfile, 'wb')
  63.  
  64. chunk = infile.read(26)
  65. data = namedunpack(chunk, ['cSignature', 'iVersionMajor', 'iVersionMinor', 'iLengthPairs', 'iLengthMS', 'iHardwareType', 'iFormat', 'iCompression', 'iShortDelayCode', 'iLongDelayCode', 'iCodemapLength'], '8sHHIIBBBBBB')
  66. outfile.write(chunk)
  67.  
  68. if data.cSignature != 'DBRAWOPL':
  69. parser.error('input file is not a DOSBox DRO file.')
  70. sys.exit(1)
  71.  
  72. # Copy over the codemap table, which has a variable length.
  73. chunk = infile.read(data.iCodemapLength)
  74. outfile.write(chunk)
  75.  
  76. pairsTotal = data.iLengthPairs
  77. pairsRead = 0
  78. quieter = OPL2Quieter(lambda level: max(level - 5, 0))
  79. while pairsRead < pairsTotal:
  80. # Read the next OPL bytes from the file
  81. chunk = infile.read(2)
  82. pairsRead += 1
  83. if not chunk: break
  84. reg, val = unpack('BB', chunk)
  85. reg, val = quieter.write(reg, val)
  86. outfile.write(pack('BB', reg, val))
  87.  
  88. infile.close()
  89. outfile.close()
Add Comment
Please, Sign In to add comment