Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 891b4ac

Browse files
committedFeb 15, 2025
NSS: fix SSG playback with default Furnace SSG output volume
By default, Furnace uses two different output volumes for ADPCM/FM and SSG. Since there is no global output volume on hardware, emulate the lower SSG volume by decreasing all volume macro steps by 1 (it roughly divides the volume by 2). Make nsstool warn about this to give users a chance to compose their module with a full output for ADPCM/FM and SSG, so no tweak is necessary.
1 parent b1c456a commit 891b4ac

File tree

1 file changed

+45
-3
lines changed

1 file changed

+45
-3
lines changed
 

‎tools/furtool.py

+45-3
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ def read_module(bs):
167167
nb_patterns = bs.u4() # skip global pattern count
168168
chips = [x for x in bs.read(32)]
169169
assert chips[:chips.index(0)] == [165] # single ym2610 chip
170-
bs.read(32 + 32 + 128) # skip chips vol, pan, flags
170+
bs.read(32) # skip chips vol
171+
bs.read(32) # skip chips pan
172+
chip_flags = unpack("32I", bs.read(128))
173+
check_chip_flags(chip_flags[0], bs)
171174
mod.name = bs.ustr()
172175
mod.author = bs.ustr()
173176
mod.pattern_len = pattern_len
@@ -219,6 +222,34 @@ def read_module(bs):
219222
return mod
220223

221224

225+
# Global tweak to compensate for default SSG output volume in Furnace
226+
HALF_SSG_VOL = False
227+
SSG_USED = False
228+
def check_chip_flags(ptr, bs):
229+
global HALF_SSG_VOL
230+
saved_pos = bs.pos
231+
bs.seek(ptr)
232+
assert bs.read(4) == b"FLAG"
233+
size = bs.u4()
234+
data = bs.read(size)
235+
datastr=bytearray(data).decode("utf-8")
236+
flags = {}
237+
for d in datastr.split("\n")[:-1]: # last item is "\0"
238+
k, v = d.split("=")
239+
flags[k]=v
240+
bs.seek(saved_pos)
241+
242+
# check if check volumes for SSG and FM/ADPCM (or the resp. Furnace defaults)
243+
ssgVol = int(flags.get("ssgVol","128"))
244+
if ssgVol == 128:
245+
HALF_SSG_VOL = True
246+
247+
# warn if this moudle uses unsupported master volumes
248+
fmVol = int(flags.get("fmVol","256"))
249+
if (not (fmVol == 256 and ssgVol == 128)) and (not (fmVol == 256 and ssgVol == 256)):
250+
print("Furnace module uses custom output volumes (FM/ADPCM: %d, SSG: %d), which is not supported in hardware.\n"
251+
"Please set the volume of FM/ADPCM and SSG tracks to 256 in Furnace in the 'Chip Manager' window"%(fmVol, ssgVol))
252+
222253

223254
@dataclass
224255
class fm_operator:
@@ -420,11 +451,18 @@ def read_ssg_macro(length, bs):
420451
"arp": 1<<2, # BIT_LOAD_NOTE
421452
}
422453

423-
autoenv=False
454+
global SSG_USED
455+
SSG_USED = True
456+
autoenv = False
424457
blocks = {}
425458
macros, loop = read_macro_data(length, bs)
426459
for code in macros:
427-
blocks[code_name[code]] = macros[code]
460+
data = macros[code]
461+
if HALF_SSG_VOL and code_name[code] == "vol":
462+
# Temporary volume adjustment when SSG channel is not mixed 100%
463+
# in the furnace module
464+
data = [max(0, x-1) for x in data]
465+
blocks[code_name[code]] = data
428466

429467
# pass: merge waveform sequence into vol & noise_tone sequences for SSG registers
430468
if "wave" in blocks:
@@ -927,6 +965,10 @@ def main():
927965

928966
if arguments.action == "instruments":
929967
generate_instruments(m, sample_map, name, bank, ins, outfd)
968+
# warn if SSG required volume tweak
969+
if SSG_USED and HALF_SSG_VOL:
970+
print("Volume of SSG channel is halved in Furnace, SSG macros were tweaked to compensate.\n"
971+
"To fix that, please set the SSG volume to 256 in Furnace in the 'Chip Manager' window.")
930972
elif arguments.action == "samples":
931973
generate_sample_map(m, smp, outfd)
932974
else:

0 commit comments

Comments
 (0)
Please sign in to comment.