2 from collections
import namedtuple
, OrderedDict
3 from json
import JSONDecoder
14 DUMP_INDIVIDUAL_BINS
= False
18 orderedJsonDecoder
= JSONDecoder(object_pairs_hook
=OrderedDict
)
22 def __init__(self
, name
, fname
, data
, sample_rate
, book
, loop
):
26 self
.sample_rate
= sample_rate
34 def __init__(self
, name
, entries
):
37 self
.entries
= entries
38 self
.name_to_entry
= {}
40 self
.name_to_entry
[e
.name
] = e
43 Book
= namedtuple("Book", ["order", "npredictors", "table"])
44 Loop
= namedtuple("Loop", ["start", "end", "count", "state"])
45 Bank
= namedtuple("Bank", ["name", "sample_bank", "json"])
49 return (val
+ (al
- 1)) & -al
53 print(msg
, file=sys
.stderr
)
55 raise Exception("re-raising exception")
59 def validate(cond
, msg
, forstr
=""):
62 msg
+= " for " + forstr
66 def strip_comments(string
):
67 string
= re
.sub(re
.compile("/\*.*?\*/", re
.DOTALL
), "", string
)
68 return re
.sub(re
.compile("//.*?\n"), "", string
)
73 fmt
= fmt
.replace('P', 'I').replace('X', '')
75 fmt
= fmt
.replace('P', 'Q').replace('X', 'xxxx')
76 return struct
.pack(ENDIAN_MARKER
+ fmt
, *args
)
84 ret |
= (num
% 10) << shift
91 exp_bits
, mantissa_bits
= struct
.unpack(">HQ", data
)
92 sign_bit
= exp_bits
& 2 ** 15
94 sign
= -1 if sign_bit
else 1
95 if exp_bits
== mantissa_bits
== 0:
97 validate(exp_bits
!= 0, "sample rate is a denormal")
98 validate(exp_bits
!= 0x7FFF, "sample rate is infinity/nan")
99 mant
= float(mantissa_bits
) / 2 ** 63
100 return sign
* mant
* pow(2, exp_bits
- 0x3FFF)
103 def parse_aifc_loop(data
):
104 validate(len(data
) == 48, "loop chunk size should be 48")
105 version
, nloops
, start
, end
, count
= struct
.unpack(">HHIIi", data
[:16])
106 validate(version
== 1, "loop version doesn't match")
107 validate(nloops
== 1, "only one loop is supported")
109 for i
in range(16, len(data
), 2):
110 state
.append(struct
.unpack(">h", data
[i
: i
+ 2])[0])
111 return Loop(start
, end
, count
, state
)
114 def parse_aifc_book(data
):
115 version
, order
, npredictors
= struct
.unpack(">hhh", data
[:6])
116 validate(version
== 1, "codebook version doesn't match")
118 len(data
) == 6 + 16 * order
* npredictors
,
119 "predictor book chunk size doesn't match",
122 for i
in range(6, len(data
), 2):
123 table
.append(struct
.unpack(">h", data
[i
: i
+ 2])[0])
124 return Book(order
, npredictors
, table
)
127 def parse_aifc(data
, name
, fname
):
128 validate(data
[:4] == b
"FORM", "must start with FORM")
129 validate(data
[8:12] == b
"AIFC", "format must be AIFC")
134 le
, = struct
.unpack(">I", data
[i
+ 4 : i
+ 8])
136 sections
.append((tp
, data
[i
: i
+ le
]))
144 for (tp
, data
) in sections
:
145 if tp
== b
"APPL" and data
[:4] == b
"stoc":
147 tp
= data
[5 : 5 + plen
]
148 data
= data
[align(5 + plen
, 2) :]
149 if tp
== b
"VADPCMCODES":
151 elif tp
== b
"VADPCMLOOPS":
154 audio_data
= data
[8:]
156 sample_rate
= parse_f80(data
[8:18])
158 validate(sample_rate
is not None, "no COMM section")
159 validate(audio_data
is not None, "no SSND section")
160 validate(vadpcm_codes
is not None, "no VADPCM table")
162 book
= parse_aifc_book(vadpcm_codes
)
163 loop
= parse_aifc_loop(vadpcm_loops
) if vadpcm_loops
is not None else None
164 return Aifc(name
, fname
, audio_data
, sample_rate
, book
, loop
)
167 class ReserveSerializer
:
174 assert isinstance(part
, (bytes
, list))
175 self
.parts
.append(part
)
176 self
.sizes
.append(len(part
))
177 self
.size
+= len(part
)
179 def reserve(self
, space
):
181 self
.parts
.append(li
)
182 self
.sizes
.append(space
)
186 def align(self
, alignment
):
187 new_size
= (self
.size
+ alignment
- 1) & -alignment
188 self
.add((new_size
- self
.size
) * b
"\0")
192 for (li
, si
) in zip(self
.parts
, self
.sizes
):
193 if isinstance(li
, list):
197 ), "unfulfilled reservation of size {}, only got {}".format(si
, len(li
))
198 flat_parts
.append(li
)
199 return b
"".join(flat_parts
)
202 class GarbageSerializer
:
204 self
.garbage_bufs
= [[]]
209 def reset_garbage_pos(self
):
210 self
.garbage_bufs
.append([])
214 assert isinstance(part
, bytes
)
215 self
.parts
.append(part
)
216 self
.garbage_bufs
[-1].append((self
.garbage_pos
, part
))
217 self
.size
+= len(part
)
218 self
.garbage_pos
+= len(part
)
220 def align(self
, alignment
):
221 new_size
= (self
.size
+ alignment
- 1) & -alignment
222 self
.add((new_size
- self
.size
) * b
"\0")
224 def garbage_at(self
, pos
):
225 # Find the last write to position pos & 0xffff, assuming a cyclic
226 # buffer of size 0x10000 where the write position is reset to 0 on
227 # each call to reset_garbage_pos.
229 for bufs
in self
.garbage_bufs
[::-1]:
230 for (bpos
, buf
) in bufs
[::-1]:
231 q
= ((bpos
+ len(buf
) - 1 - pos
) & ~
0xFFFF) + pos
236 def align_garbage(self
, alignment
):
237 while self
.size
% alignment
!= 0:
238 self
.add(bytes([self
.garbage_at(self
.garbage_pos
)]))
241 return b
"".join(self
.parts
)
244 def validate_json_format(json
, fmt
, forstr
=""):
245 constructor_to_name
= {
249 float: "a floating point number",
252 for key
, tp
in fmt
.items():
253 validate(key
in json
, 'missing key "' + key
+ '"', forstr
)
254 if isinstance(tp
, list):
255 validate_int_in_range(json
[key
], tp
[0], tp
[1], '"' + key
+ '"', forstr
)
258 isinstance(json
[key
], tp
)
259 or (tp
== float and isinstance(json
[key
], int)),
260 '"{}" must be {}'.format(key
, constructor_to_name
[tp
]),
265 def validate_int_in_range(val
, lo
, hi
, msg
, forstr
=""):
266 validate(isinstance(val
, int), "{} must be an integer".format(msg
), forstr
)
268 lo
<= val
<= hi
, "{} must be in range {} to {}".format(msg
, lo
, hi
), forstr
272 def validate_sound(json
, sample_bank
, forstr
=""):
273 validate_json_format(json
, {"sample": str}, forstr
)
275 validate_json_format(json
, {"tuning": float}, forstr
)
277 json
["sample"] in sample_bank
.name_to_entry
,
278 "reference to sound {} which isn't found in sample bank {}".format(
279 json
["sample"], sample_bank
.name
285 def validate_bank_toplevel(json
):
286 validate(isinstance(json
, dict), "must have a top-level object")
287 validate_json_format(
293 "instrument_list": list,
298 def normalize_sound_json(json
):
299 # Convert {"sound": "str"} into {"sound": {"sample": "str"}}
301 for inst
in json
["instruments"].values():
302 if isinstance(inst
, list):
304 fixup
.append((drum
, "sound"))
306 fixup
.append((inst
, "sound_lo"))
307 fixup
.append((inst
, "sound"))
308 fixup
.append((inst
, "sound_hi"))
309 for (obj
, key
) in fixup
:
310 if isinstance(obj
, dict) and isinstance(obj
.get(key
, None), str):
311 obj
[key
] = {"sample": obj
[key
]}
314 def validate_bank(json
, sample_bank
):
317 isinstance(json
["date"], str)
318 and re
.match(r
"[0-9]{4}-[0-9]{2}-[0-9]{2}\Z", json
["date"]),
319 "date must have format yyyy-mm-dd",
322 for key
, env
in json
["envelopes"].items():
323 validate(isinstance(env
, list), 'envelope "' + key
+ '" must be an array')
326 if entry
in ["stop", "hang", "restart"]:
330 isinstance(entry
, list) and len(entry
) == 2,
331 'envelope entry in "'
333 + '" must be a list of length 2, or one of stop/hang/restart',
335 if entry
[0] == "goto":
336 validate_int_in_range(
337 entry
[1], 0, len(env
) - 2, "envelope goto target out of range:"
341 validate_int_in_range(
342 entry
[0], 1, 2 ** 16 - 4, "envelope entry's first part"
344 validate_int_in_range(
345 entry
[1], 0, 2 ** 16 - 1, "envelope entry's second part"
349 last_fine
, 'envelope "{}" must end with stop/hang/restart/goto'.format(key
)
354 instrument_names
= set()
355 for name
, inst
in json
["instruments"].items():
356 if name
== "percussion":
357 validate(isinstance(inst
, list), "drums entry must be a list")
360 validate(isinstance(inst
, dict), "instrument entry must be an object")
361 instruments
.append((name
, inst
))
362 instrument_names
.add(name
)
365 validate(isinstance(drum
, dict), "drum entry must be an object")
366 validate_json_format(
368 {"release_rate": [0, 255], "pan": [0, 128], "envelope": str, "sound": dict},
370 validate_sound(drum
["sound"], sample_bank
)
372 drum
["envelope"] in json
["envelopes"],
373 "reference to non-existent envelope " + drum
["envelope"],
379 for name
, inst
in instruments
:
380 forstr
= "instrument " + name
381 for lohi
in ["lo", "hi"]:
382 nr
= "normal_range_" + lohi
385 validate(so
in inst
, nr
+ " is specified, but not " + so
, forstr
)
387 validate(nr
in inst
, so
+ " is specified, but not " + nr
, forstr
)
390 if "normal_range_lo" not in inst
:
391 inst
["normal_range_lo"] = 0
392 if "normal_range_hi" not in inst
:
393 inst
["normal_range_hi"] = 127
395 validate_json_format(
398 "release_rate": [0, 255],
400 "normal_range_lo": [0, 127],
401 "normal_range_hi": [0, 127],
411 isinstance(inst
["ifdef"], list)
412 and all(isinstance(x
, str) for x
in inst
["ifdef"]),
413 '"ifdef" must be an array of strings',
417 inst
["normal_range_lo"] <= inst
["normal_range_hi"],
418 "normal_range_lo > normal_range_hi",
422 inst
["envelope"] in json
["envelopes"],
423 "reference to non-existent envelope " + inst
["envelope"],
426 for key
in ["sound_lo", "sound", "sound_hi"]:
427 if inst
[key
] is no_sound
:
430 validate_sound(inst
[key
], sample_bank
, forstr
)
432 seen_instruments
= set()
433 for inst
in json
["instrument_list"]:
437 isinstance(inst
, str),
438 "instrument list should contain only strings and nulls",
441 inst
in instrument_names
, "reference to non-existent instrument " + inst
444 inst
not in seen_instruments
, inst
+ " occurs twice in the instrument list"
446 seen_instruments
.add(inst
)
448 for inst
in instrument_names
:
449 validate(inst
in seen_instruments
, "unreferenced instrument " + inst
)
452 def apply_version_diffs(json
, defines
):
453 if "VERSION_EU" in defines
and isinstance(json
.get("date", None), str):
454 json
["date"] = json
["date"].replace("1996-03-19", "1996-06-24")
456 ifdef_removed
= set()
457 for key
, inst
in json
["instruments"].items():
459 isinstance(inst
, dict)
460 and isinstance(inst
.get("ifdef", None), list)
461 and all(d
not in defines
for d
in inst
["ifdef"])
463 ifdef_removed
.add(key
)
464 for key
in ifdef_removed
:
465 del json
["instruments"][key
]
466 json
["instrument_list"].remove(key
)
469 def mark_sample_bank_uses(bank
):
470 bank
.sample_bank
.uses
.append(bank
)
473 bank
.sample_bank
.name_to_entry
[name
].used
= True
475 for inst
in bank
.json
["instruments"].values():
476 if isinstance(inst
, list):
478 mark_used(drum
["sound"]["sample"])
480 if "sound_lo" in inst
:
481 mark_used(inst
["sound_lo"]["sample"])
482 mark_used(inst
["sound"]["sample"])
483 if "sound_hi" in inst
:
484 mark_used(inst
["sound_hi"]["sample"])
487 def serialize_ctl(bank
, base_ser
):
492 for inst
in json
["instruments"].values():
493 if isinstance(inst
, list):
496 instruments
.append(inst
)
498 y
, m
, d
= map(int, json
.get("date", "0000-00-00").split("-"))
499 date
= y
* 10000 + m
* 100 + d
503 len(json
["instrument_list"]),
505 1 if len(bank
.sample_bank
.uses
) > 1 else 0,
510 ser
= ReserveSerializer()
512 drum_pos_buf
= ser
.reserve(WORD_BYTES
)
514 ser
.add(b
"\0" * WORD_BYTES
)
517 inst_pos_buf
= ser
.reserve(WORD_BYTES
* len(json
["instrument_list"]))
521 for inst
in json
["instruments"].values():
522 if isinstance(inst
, list):
524 used_samples
.append(drum
["sound"]["sample"])
526 if "sound_lo" in inst
:
527 used_samples
.append(inst
["sound_lo"]["sample"])
528 used_samples
.append(inst
["sound"]["sample"])
529 if "sound_hi" in inst
:
530 used_samples
.append(inst
["sound_hi"]["sample"])
532 sample_name_to_addr
= {}
533 for name
in used_samples
:
534 if name
in sample_name_to_addr
:
536 sample_name_to_addr
[name
] = ser
.size
537 aifc
= bank
.sample_bank
.name_to_entry
[name
]
538 sample_len
= len(aifc
.data
)
541 ser
.add(pack("PP", 0, aifc
.offset
))
542 loop_addr_buf
= ser
.reserve(WORD_BYTES
)
543 book_addr_buf
= ser
.reserve(WORD_BYTES
)
544 ser
.add(pack("I", align(sample_len
, 2)))
548 book_addr_buf
.append(pack("P", ser
.size
))
549 ser
.add(pack("ii", aifc
.book
.order
, aifc
.book
.npredictors
))
550 for x
in aifc
.book
.table
:
551 ser
.add(pack("h", x
))
555 loop_addr_buf
.append(pack("P", ser
.size
))
556 if aifc
.loop
is None:
557 assert sample_len
% 9 in [0, 1]
558 end
= sample_len
// 9 * 16 + (sample_len
% 2) + (sample_len
% 9)
559 ser
.add(pack("IIiI", 0, end
, 0, 0))
561 ser
.add(pack("IIiI", aifc
.loop
.start
, aifc
.loop
.end
, aifc
.loop
.count
, 0))
562 assert aifc
.loop
.count
!= 0
563 for x
in aifc
.loop
.state
:
564 ser
.add(pack("h", x
))
567 env_name_to_addr
= {}
568 for name
, env
in json
["envelopes"].items():
569 env_name_to_addr
[name
] = ser
.size
573 elif entry
== "hang":
574 entry
= [2 ** 16 - 1, 0]
575 elif entry
== "restart":
576 entry
= [2 ** 16 - 3, 0]
577 elif entry
[0] == "goto":
578 entry
[0] = 2 ** 16 - 2
579 # Envelopes are always written as big endian, to match sequence files
580 # which are byte blobs and can embed envelopes.
581 ser
.add(struct
.pack(">HH", *entry
))
584 def ser_sound(sound
):
586 0 if sound
["sample"] is None else sample_name_to_addr
[sound
["sample"]]
588 if "tuning" in sound
:
589 tuning
= sound
["tuning"]
591 aifc
= bank
.sample_bank
.name_to_entry
[sound
["sample"]]
592 tuning
= aifc
.sample_rate
/ 32000
593 ser
.add(pack("PfX", sample_addr
, tuning
))
595 no_sound
= {"sample": None, "tuning": 0.0}
597 inst_name_to_pos
= {}
598 for name
, inst
in json
["instruments"].items():
599 if isinstance(inst
, list):
601 inst_name_to_pos
[name
] = ser
.size
602 env_addr
= env_name_to_addr
[inst
["envelope"]]
607 inst
.get("normal_range_lo", 0),
608 inst
.get("normal_range_hi", 127),
609 inst
["release_rate"],
613 ser_sound(inst
.get("sound_lo", no_sound
))
614 ser_sound(inst
["sound"])
615 ser_sound(inst
.get("sound_hi", no_sound
))
618 for name
in json
["instrument_list"]:
620 inst_pos_buf
.append(pack("P", 0))
622 inst_pos_buf
.append(pack("P", inst_name_to_pos
[name
]))
627 drum_poses
.append(ser
.size
)
628 ser
.add(pack("BBBBX", drum
["release_rate"], drum
["pan"], 0, 0))
629 ser_sound(drum
["sound"])
630 env_addr
= env_name_to_addr
[drum
["envelope"]]
631 ser
.add(pack("P", env_addr
))
634 drum_pos_buf
.append(pack("P", ser
.size
))
635 for pos
in drum_poses
:
636 ser
.add(pack("P", pos
))
639 base_ser
.add(ser
.finish())
642 def serialize_tbl(sample_bank
, ser
):
643 ser
.reset_garbage_pos()
645 for aifc
in sample_bank
.entries
:
649 aifc
.offset
= ser
.size
- base_addr
652 ser
.align_garbage(16)
655 def serialize_seqfile(entries
, serialize_entry
, entry_list
, magic
, extra_padding
=True):
656 ser
= ReserveSerializer()
657 ser
.add(pack("HHX", magic
, len(entry_list
)))
658 table
= ser
.reserve(len(entry_list
) * 2 * WORD_BYTES
)
660 data_start
= ser
.size
662 ser2
= GarbageSerializer()
665 for entry
in entries
:
666 entry_offsets
.append(ser2
.size
)
667 serialize_entry(entry
, ser2
)
668 entry_lens
.append(ser2
.size
- entry_offsets
[-1])
669 ser
.add(ser2
.finish())
674 for ent
in entry_list
:
675 table
.append(pack("P", entry_offsets
[ent
] + data_start
))
676 table
.append(pack("IX", entry_lens
[ent
]))
680 def validate_and_normalize_sequence_json(json
, bank_names
, defines
):
681 validate(isinstance(json
, dict), "must have a top-level object")
682 if "comment" in json
:
684 for key
, seq
in json
.items():
685 if isinstance(seq
, dict):
686 validate_json_format(seq
, {"ifdef": list, "banks": list}, key
)
688 all(isinstance(x
, str) for x
in seq
["ifdef"]),
689 '"ifdef" must be an array of strings',
692 if all(d
not in defines
for d
in seq
["ifdef"]):
697 if isinstance(seq
, list):
700 isinstance(x
, str), "bank list must be an array of strings", key
703 x
in bank_names
, "reference to non-existing sound bank " + x
, key
706 validate(seq
is None, "bad JSON type, expected null, array or object", key
)
710 inputs
, out_filename
, out_bank_sets
, sound_bank_dir
, seq_json
, defines
713 [os
.path
.splitext(os
.path
.basename(x
))[0] for x
in os
.listdir(sound_bank_dir
)]
717 with
open(seq_json
, "r") as inf
:
719 data
= strip_comments(data
)
720 json
= orderedJsonDecoder
.decode(data
)
721 validate_and_normalize_sequence_json(json
, bank_names
, defines
)
723 except Exception as e
:
724 fail("failed to parse " + str(seq_json
) + ": " + str(e
))
726 inputs
.sort(key
=lambda f
: os
.path
.basename(f
))
729 name
= os
.path
.splitext(os
.path
.basename(fname
))[0]
730 if name
in name_to_fname
:
735 + name_to_fname
[name
]
736 + " conflict. Remove one of them."
738 name_to_fname
[name
] = fname
741 "Sequence file " + fname
+ " is not mentioned in sequences.json. "
742 "Either assign it a list of sound banks, or set it to null to "
743 "explicitly leave it out from the build."
746 for key
, seq
in json
.items():
747 if key
not in name_to_fname
and seq
is not None:
749 "sequences.json assigns sound banks to "
751 + ", but there is no such sequence file. Either remove the entry (or "
752 "set it to null), or create sound/sequences/" + key
+ ".m64."
757 ind
= int(key
.split("_")[0], 16)
758 while len(ind_to_name
) <= ind
:
759 ind_to_name
.append(None)
760 if ind_to_name
[ind
] is not None:
766 + " have the same index. Renumber or delete one of them."
768 ind_to_name
[ind
] = key
770 while ind_to_name
and json
.get(ind_to_name
[-1], None) is None:
773 def serialize_file(name
, ser
):
774 if json
.get(name
, None) is None:
776 ser
.reset_garbage_pos()
777 with
open(name_to_fname
[name
], "rb") as f
:
779 ser
.align_garbage(16)
781 with
open(out_filename
, "wb") as f
:
782 n
= range(len(ind_to_name
))
783 f
.write(serialize_seqfile(ind_to_name
, serialize_file
, n
, 3, False))
785 with
open(out_bank_sets
, "wb") as f
:
786 ser
= ReserveSerializer()
787 table
= ser
.reserve(len(ind_to_name
) * 2)
788 for name
in ind_to_name
:
789 bank_set
= json
.get(name
, None)
792 table
.append(pack("H", ser
.size
))
793 ser
.add(bytes([len(bank_set
)]))
794 for bank
in bank_set
[::-1]:
795 ser
.add(bytes([bank_names
.index(bank
)]))
797 f
.write(ser
.finish())
807 print_samples
= False
808 sequences_out_file
= None
811 for i
, a
in enumerate(sys
.argv
[1:], 1):
815 if a
== "--help" or a
== "-h":
818 cpp_command
= sys
.argv
[i
+ 1]
821 defines
.append(sys
.argv
[i
+ 1])
823 elif a
== "--endian":
824 endian
= sys
.argv
[i
+ 1]
827 elif endian
== "little":
829 elif endian
== "native":
832 fail("--endian takes argument big, little or native")
834 elif a
== "--bitwidth":
835 bitwidth
= sys
.argv
[i
+ 1]
836 if bitwidth
== 'native':
837 WORD_BYTES
= struct
.calcsize('P')
839 if bitwidth
not in ['32', '64']:
840 fail("--bitwidth takes argument 32, 64 or native")
841 WORD_BYTES
= int(bitwidth
) // 8
843 elif a
.startswith("-D"):
844 defines
.append(a
[2:])
845 elif a
== "--stack-trace":
847 elif a
== "--print-samples":
849 elif a
== "--sequences":
850 sequences_out_file
= sys
.argv
[i
+ 1]
851 bank_sets_out_file
= sys
.argv
[i
+ 2]
852 sound_bank_dir
= sys
.argv
[i
+ 3]
853 sequence_json
= sys
.argv
[i
+ 4]
855 elif a
.startswith("-"):
856 print("Unrecognized option " + a
)
861 defines_set
= {d
.split("=")[0] for d
in defines
}
863 if sequences_out_file
is not None and not need_help
:
874 if need_help
or len(args
) != 4:
876 "Usage: {} <samples dir> <sound bank dir>"
877 " <out .ctl file> <out .tbl file>"
878 " [--cpp <preprocessor>]"
881 " | --sequences <out sequence .bin> <out bank sets .bin> <sound bank dir> "
882 "<sequences.json> <inputs...>".format(sys
.argv
[0])
884 sys
.exit(0 if need_help
else 1)
886 sample_bank_dir
= args
[0]
887 sound_bank_dir
= args
[1]
888 ctl_data_out
= args
[2]
889 tbl_data_out
= args
[3]
893 name_to_sample_bank
= {}
895 sample_bank_names
= sorted(os
.listdir(sample_bank_dir
))
896 for name
in sample_bank_names
:
897 dir = os
.path
.join(sample_bank_dir
, name
)
898 if not os
.path
.isdir(dir):
901 for f
in sorted(os
.listdir(dir)):
902 fname
= os
.path
.join(dir, f
)
903 if not f
.endswith(".aifc"):
906 with
open(fname
, "rb") as inf
:
908 entries
.append(parse_aifc(data
, f
[:-5], fname
))
909 except Exception as e
:
910 fail("malformed AIFC file " + fname
+ ": " + str(e
))
912 sample_bank
= SampleBank(name
, entries
)
913 sample_banks
.append(sample_bank
)
914 name_to_sample_bank
[name
] = sample_bank
916 bank_names
= sorted(os
.listdir(sound_bank_dir
))
918 fname
= os
.path
.join(sound_bank_dir
, f
)
919 if not f
.endswith(".json"):
924 data
= subprocess
.run(
925 [cpp_command
, fname
] + ["-D" + x
for x
in defines
],
926 stdout
=subprocess
.PIPE
,
930 with
open(fname
, "r") as inf
:
932 data
= strip_comments(data
)
933 bank_json
= orderedJsonDecoder
.decode(data
)
935 validate_bank_toplevel(bank_json
)
936 apply_version_diffs(bank_json
, defines_set
)
937 normalize_sound_json(bank_json
)
939 sample_bank_name
= bank_json
["sample_bank"]
941 sample_bank_name
in name_to_sample_bank
,
942 "sample bank " + sample_bank_name
+ " not found",
944 sample_bank
= name_to_sample_bank
[sample_bank_name
]
946 validate_bank(bank_json
, sample_bank
)
948 bank
= Bank(f
[:-5], sample_bank
, bank_json
)
949 mark_sample_bank_uses(bank
)
952 except Exception as e
:
953 fail("failed to parse bank " + fname
+ ": " + str(e
))
955 sample_banks
= [b
for b
in sample_banks
if b
.uses
]
956 sample_banks
.sort(key
=lambda b
: b
.uses
[0].name
)
957 sample_bank_index
= {}
958 for sample_bank
in sample_banks
:
959 sample_bank_index
[sample_bank
] = len(sample_bank_index
)
961 with
open(tbl_data_out
, "wb") as out
:
966 [sample_bank_index
[x
.sample_bank
] for x
in banks
],
971 with
open(ctl_data_out
, "wb") as out
:
972 if DUMP_INDIVIDUAL_BINS
:
973 # Debug logic, may simplify diffing
974 os
.makedirs("ctl/", exist_ok
=True)
976 with
open("ctl/" + b
.name
+ ".bin", "wb") as f
:
977 ser
= GarbageSerializer()
978 serialize_ctl(b
, ser
)
979 f
.write(ser
.finish())
980 print("wrote to ctl/")
983 serialize_seqfile(banks
, serialize_ctl
, list(range(len(banks
))), TYPE_CTL
)
987 for sample_bank
in sample_banks
:
988 for entry
in sample_bank
.entries
:
993 if __name__
== "__main__":