Add music player source
This commit is contained in:
parent
3f4765029b
commit
c006e1a0a2
|
|
@ -97,6 +97,7 @@ C1541 = c1541
|
||||||
CC1541 = ../../tools/cc1541/cc1541
|
CC1541 = ../../tools/cc1541/cc1541
|
||||||
ZX02 = zx02/build/zx02
|
ZX02 = zx02/build/zx02
|
||||||
ZX02_SRC = zx02
|
ZX02_SRC = zx02
|
||||||
|
FURC64 = furC64
|
||||||
PYTHON = python3
|
PYTHON = python3
|
||||||
|
|
||||||
MKDIR = mkdir -p
|
MKDIR = mkdir -p
|
||||||
|
|
@ -130,6 +131,7 @@ DISKIMAGE = $(BUILDDIR)/$(NAME)-$(_PLATFORM_).d64
|
||||||
AS_FLAGS = -Wa -I../../../shared -I ../../include -u __EXEHDR__
|
AS_FLAGS = -Wa -I../../../shared -I ../../include -u __EXEHDR__
|
||||||
|
|
||||||
ZX0PRGS = \
|
ZX0PRGS = \
|
||||||
|
use_this_sid.zx0.prg \
|
||||||
badguy.zx0.prg \
|
badguy.zx0.prg \
|
||||||
title_320-prepared.zx0.prg \
|
title_320-prepared.zx0.prg \
|
||||||
tower.zx0.prg \
|
tower.zx0.prg \
|
||||||
|
|
@ -174,11 +176,11 @@ endif
|
||||||
|
|
||||||
diskimage: $(DISKIMAGE)
|
diskimage: $(DISKIMAGE)
|
||||||
|
|
||||||
$(DISKIMAGE): $(ASSEMBLE) $(CC1541) $(ZX0PRGS) $(LZPRGS) use_this_sid.bin font.bin
|
$(DISKIMAGE): $(ASSEMBLE) $(CC1541) $(ZX0PRGS) $(LZPRGS) font.bin
|
||||||
$(RM) $@
|
$(RM) $@
|
||||||
$(CC1541) -n "otomata labs" -i " 2025" \
|
$(CC1541) -n "otomata labs" -i " 2025" \
|
||||||
-f "ys2intro" -w $< \
|
-f "ys2intro" -w $< \
|
||||||
-f "sid" -w use_this_sid.bin \
|
-f "song0" -w use_this_sid.zx0.prg \
|
||||||
-f "badguy" -w badguy.zx0.prg \
|
-f "badguy" -w badguy.zx0.prg \
|
||||||
-f "font" -w font.bin \
|
-f "font" -w font.bin \
|
||||||
-f "intrbmp" -w title_320-prepared.zx0.prg \
|
-f "intrbmp" -w title_320-prepared.zx0.prg \
|
||||||
|
|
@ -224,6 +226,10 @@ $(ZX02):
|
||||||
$(PRINTF) "\x00\x90" | cat - $@.tmp > $@
|
$(PRINTF) "\x00\x90" | cat - $@.tmp > $@
|
||||||
$(RM) $@.tmp
|
$(RM) $@.tmp
|
||||||
|
|
||||||
|
use_this_sid.bin: ys2_port_legato.fur
|
||||||
|
cd $(FURC64) && ./convert.sh $(abspath $<)
|
||||||
|
cp $(FURC64)/asm/song.bin $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-$(RM) $(ZX0PRGS) $(LZPRGS)
|
-$(RM) $(ZX0PRGS) $(LZPRGS)
|
||||||
-$(RM) *.o $(ASSEMBLE) $(DISKIMAGE)
|
-$(RM) *.o $(ASSEMBLE) $(DISKIMAGE)
|
||||||
|
|
|
||||||
8
loader/samples/minexample/furC64/.gitignore
vendored
Normal file
8
loader/samples/minexample/furC64/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
__pycache__
|
||||||
|
asm/song.asm
|
||||||
|
asm/song.bin
|
||||||
|
*.o
|
||||||
|
*.lbl
|
||||||
|
*.lst
|
||||||
|
*.map
|
||||||
|
*.prg
|
||||||
42
loader/samples/minexample/furC64/README.md
Normal file
42
loader/samples/minexample/furC64/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# furC64
|
||||||
|
a C64/SID sound driver for Furnace
|
||||||
|
|
||||||
|
### **THIS SOUND DRIVER IS CURRENTLY A WIP**
|
||||||
|
|
||||||
|
A SID driver that's easy to make music with? It's more likely than you think.
|
||||||
|
|
||||||
|
* You have to have [Python](https://www.python.org/) and the [CC65 toolchain](https://cc65.github.io/) installed
|
||||||
|
* You **have** to set the pitch linearity option to "None". You can do this by going to `window -> song -> compatability flags -> Pitch/Playback -> Pitch linearity` and then setting the option to "None".
|
||||||
|
|
||||||
|
* The driver only supports **arpeggio, waveform, duty and cutoff** macros in each instrument and it DOESN'T support LFO and ADSR macros nor delay and step length, **although you can use LFO macros in the duty and cutoff macros (as in range-sweeping)**
|
||||||
|
|
||||||
|
* The furC64 driver only supports these effects:
|
||||||
|
* 00xx: arpeggio
|
||||||
|
* 01xx: pitch slide up
|
||||||
|
* 02xx: pitch slide down
|
||||||
|
* 03xx: portamento
|
||||||
|
* 04xx: vibrato
|
||||||
|
* 09xx: set speed 1
|
||||||
|
* 0Bxx: jump to pattern
|
||||||
|
* 0Dxx: jump to next pattern
|
||||||
|
* 0Fxx: set speed 2
|
||||||
|
* 1Axx: disable/enable envelope reset
|
||||||
|
* 1Bxx: reset cutoff
|
||||||
|
* 1Cxx: reset pulse-width
|
||||||
|
* 4xxx: set filter cutoff
|
||||||
|
* E1xx: note slide up
|
||||||
|
* E2xx: note slide down
|
||||||
|
* E5xx: note fine-pitch
|
||||||
|
* EAxx: legato
|
||||||
|
* ECxx: note cut
|
||||||
|
|
||||||
|
when you've finished / want to test out this driver:
|
||||||
|
* open the terminal/command prompt **to the furC64 directory**
|
||||||
|
* run `convert.sh your_fur_file.fur` or `convert.bat file.fur` (depending on your OS)
|
||||||
|
* in the `furC64/asm` directory you'll hopefully see a file called **`furC64-test.prg`**
|
||||||
|
* that's your .prg file that you can run on hardware or on an emulator like VICE!
|
||||||
|
|
||||||
|
Hopefully you'll have fun with this driver alongside [furNES](https://github.com/AnnoyedArt1256/furNES) :D
|
||||||
|
|
||||||
|
Libraries used: chipchune
|
||||||
|
|
||||||
16
loader/samples/minexample/furC64/asm/bin.cfg
Normal file
16
loader/samples/minexample/furC64/asm/bin.cfg
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
FEATURES {
|
||||||
|
STARTADDRESS: default = $0801;
|
||||||
|
}
|
||||||
|
MEMORY {
|
||||||
|
ZP: file = "", start = $0002, size = $00FE, define = yes;
|
||||||
|
MAIN: file = "", start = %S, size = $A000 - %S;
|
||||||
|
PLAYER: file = %O, start = $A000, size = $5FFA;
|
||||||
|
}
|
||||||
|
SEGMENTS {
|
||||||
|
ZEROPAGE: load = ZP, type = zp, optional = yes;
|
||||||
|
CODE: load = MAIN, type = rw;
|
||||||
|
RODATA: load = MAIN, type = ro, optional = yes;
|
||||||
|
DATA: load = MAIN, type = rw, optional = yes;
|
||||||
|
BSS: load = MAIN, type = bss, optional = yes, define = yes;
|
||||||
|
PLAYER: load = PLAYER, type = rw, define = yes;
|
||||||
|
}
|
||||||
22
loader/samples/minexample/furC64/asm/exe.cfg
Normal file
22
loader/samples/minexample/furC64/asm/exe.cfg
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
FEATURES {
|
||||||
|
STARTADDRESS: default = $0801;
|
||||||
|
}
|
||||||
|
SYMBOLS {
|
||||||
|
__LOADADDR__: type = import;
|
||||||
|
}
|
||||||
|
MEMORY {
|
||||||
|
ZP: file = "", start = $0002, size = $00FE, define = yes;
|
||||||
|
LOADADDR: file = %O, start = %S - 2, size = $0002;
|
||||||
|
MAIN: file = %O, start = %S, size = $A000 - %S;
|
||||||
|
PLAYER: file = %O, start = $A000, size = $5FFA;
|
||||||
|
}
|
||||||
|
SEGMENTS {
|
||||||
|
ZEROPAGE: load = ZP, type = zp, optional = yes;
|
||||||
|
LOADADDR: load = LOADADDR, type = ro;
|
||||||
|
EXEHDR: load = MAIN, type = ro, optional = yes;
|
||||||
|
CODE: load = MAIN, type = rw;
|
||||||
|
RODATA: load = MAIN, type = ro, optional = yes;
|
||||||
|
DATA: load = MAIN, type = rw, optional = yes;
|
||||||
|
BSS: load = MAIN, type = bss, optional = yes, define = yes;
|
||||||
|
PLAYER: load = MAIN, run = PLAYER, type = rw, define = yes;
|
||||||
|
}
|
||||||
2181
loader/samples/minexample/furC64/asm/furC64.asm
Normal file
2181
loader/samples/minexample/furC64/asm/furC64.asm
Normal file
File diff suppressed because it is too large
Load diff
3
loader/samples/minexample/furC64/asm/note_hi.bin
Normal file
3
loader/samples/minexample/furC64/asm/note_hi.bin
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
|
"$')+.147:>AEINRW\bhnu|<7C><><EFBFBD><EFBFBD>ク皇俑褻<E4BF91>
|
||||||
1
loader/samples/minexample/furC64/asm/note_lo.bin
Normal file
1
loader/samples/minexample/furC64/asm/note_lo.bin
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
&8K^s‰¡¹Ôð
,Mq–½çBs¨àY›â,{Î'„çQÀ6³7ÄYö<59>N Ï¢<C38F>mgoˆ²í:œŸDÚÎßdÚu8&?‰´œ¿"È´ëqLh8E<7F>hÖã˜ÿ$ÿ
|
||||||
18
loader/samples/minexample/furC64/chipchune/__init__.py
Normal file
18
loader/samples/minexample/furC64/chipchune/__init__.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
"""
|
||||||
|
:mod:`chipchune` is a Python library for manipulating several
|
||||||
|
different kinds of chiptune music files.
|
||||||
|
|
||||||
|
Currently supports:
|
||||||
|
- Furnace (:mod:`chipchune.furnace`) (almost!)
|
||||||
|
|
||||||
|
Plans to support:
|
||||||
|
- DefleMask (:mod:`chipchune.deflemask`)
|
||||||
|
- FamiTracker (:mod:`chipchune.famitracker`)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
`pip install git+https://github.com/ZoomTen/chipchune@master`
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "0.0.1"
|
||||||
98
loader/samples/minexample/furC64/chipchune/_util.py
Normal file
98
loader/samples/minexample/furC64/chipchune/_util.py
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
import struct
|
||||||
|
from enum import Enum
|
||||||
|
from typing import BinaryIO, Any, cast
|
||||||
|
import io
|
||||||
|
|
||||||
|
known_sizes = {
|
||||||
|
'c': 1,
|
||||||
|
'b': 1, 'B': 1,
|
||||||
|
'?': 1,
|
||||||
|
'h': 2, 'H': 2,
|
||||||
|
'i': 4, 'I': 4,
|
||||||
|
'l': 4, 'L': 4,
|
||||||
|
'q': 8, 'Q': 8,
|
||||||
|
'e': 2, 'f': 4,
|
||||||
|
'd': 8
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EnumShowNameOnly(Enum):
|
||||||
|
"""
|
||||||
|
Just an Enum, except its string repr is
|
||||||
|
just the enum's name
|
||||||
|
"""
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
|
class EnumValueEquals(Enum):
|
||||||
|
"""
|
||||||
|
Enum that can be compared to its raw value.
|
||||||
|
"""
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
return cast(bool, self.value == other)
|
||||||
|
|
||||||
|
|
||||||
|
def truthy_to_boolbyte(value: Any) -> bytes:
|
||||||
|
"""
|
||||||
|
If value is truthy, output b'\x01'. Else output b'\x00'.
|
||||||
|
|
||||||
|
:param value: anything
|
||||||
|
"""
|
||||||
|
if value:
|
||||||
|
return b'\x01'
|
||||||
|
else:
|
||||||
|
return b'\x00'
|
||||||
|
|
||||||
|
|
||||||
|
# these are just to make the typehinter happy
|
||||||
|
# cast(dolphin, foobar) should've been named trust_me_bro_im_a(dolphin, foobar)
|
||||||
|
|
||||||
|
|
||||||
|
def read_int(file: BinaryIO, signed: bool = False) -> int:
|
||||||
|
"""
|
||||||
|
4 bytes
|
||||||
|
"""
|
||||||
|
if signed:
|
||||||
|
return cast(int, struct.unpack('<i', file.read(known_sizes['i']))[0])
|
||||||
|
return cast(int, struct.unpack('<I', file.read(known_sizes['I']))[0])
|
||||||
|
|
||||||
|
|
||||||
|
def read_short(file: BinaryIO, signed: bool = False) -> int:
|
||||||
|
"""
|
||||||
|
2 bytes
|
||||||
|
"""
|
||||||
|
if signed:
|
||||||
|
return cast(int, struct.unpack('<h', file.read(known_sizes['h']))[0])
|
||||||
|
return cast(int, struct.unpack('<H', file.read(known_sizes['H']))[0])
|
||||||
|
|
||||||
|
|
||||||
|
def read_byte(file: BinaryIO, signed: bool = False) -> int:
|
||||||
|
"""
|
||||||
|
1 bytes
|
||||||
|
"""
|
||||||
|
if signed:
|
||||||
|
return cast(int, struct.unpack('<b', file.read(known_sizes['b']))[0])
|
||||||
|
return cast(int, struct.unpack('<B', file.read(known_sizes['B']))[0])
|
||||||
|
|
||||||
|
|
||||||
|
def read_float(file: BinaryIO) -> float:
|
||||||
|
"""
|
||||||
|
4 bytes
|
||||||
|
"""
|
||||||
|
return cast(float, struct.unpack('<f', file.read(known_sizes['f']))[0])
|
||||||
|
|
||||||
|
|
||||||
|
def read_str(file: BinaryIO) -> str:
|
||||||
|
"""
|
||||||
|
variable string (ends in \\x00)
|
||||||
|
"""
|
||||||
|
buffer = bytearray()
|
||||||
|
char = file.read(1)
|
||||||
|
while char != b'\x00':
|
||||||
|
buffer += char
|
||||||
|
char = file.read(1)
|
||||||
|
return buffer.decode('utf-8')
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
soon!
|
||||||
|
"""
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
soon!
|
||||||
|
"""
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
"""
|
||||||
|
Tools to manipulate Furnace .fur files.
|
||||||
|
|
||||||
|
- :mod:`chipchune.furnace.module`: Tools to inspect and manipulate module files.
|
||||||
|
- :mod:`chipchune.furnace.instrument`: Tools to inspect and manipulate instrument data from within or without the module.
|
||||||
|
- :mod:`chipchune.furnace.sample`: Tools to inspect and manipulate sample data (might be merged with inst?)
|
||||||
|
- :mod:`chipchune.furnace.wavetable`: Tools to inspect and manipulate wavetable data
|
||||||
|
- :mod:`chipchune.furnace.enums`: Various constants that apply to Furnace.
|
||||||
|
- :mod:`chipchune.furnace.data_types`: Various data types that apply to Furnace.
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
from chipchune.furnace.module import FurnaceModule
|
||||||
|
|
||||||
|
module = FurnaceModule("tests/samples/furnace/skate_or_die.143.fur")
|
||||||
|
|
||||||
|
pattern = module.get_pattern(0, 0, 0)
|
||||||
|
|
||||||
|
print(pattern.as_clipboard())
|
||||||
|
|
||||||
|
for row in pattern.data:
|
||||||
|
print(row)
|
||||||
|
"""
|
||||||
706
loader/samples/minexample/furC64/chipchune/furnace/data_types.py
Normal file
706
loader/samples/minexample/furC64/chipchune/furnace/data_types.py
Normal file
|
|
@ -0,0 +1,706 @@
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Tuple, List, TypedDict, Any, Union, Dict
|
||||||
|
|
||||||
|
from .enums import (
|
||||||
|
ChipType, LinearPitch, LoopModality, DelayBehavior, JumpTreatment, InputPortSet, OutputPortSet,
|
||||||
|
InstrumentType, MacroCode, OpMacroCode, MacroType, MacroItem, GBHwCommand, WaveFX, ESFilterMode,
|
||||||
|
SNESSusMode, GainMode, Note
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# modules
|
||||||
|
@dataclass
|
||||||
|
class ChipInfo:
|
||||||
|
"""
|
||||||
|
Information on a single chip.
|
||||||
|
"""
|
||||||
|
type: ChipType
|
||||||
|
#: shall be a simple dict, no enums needed
|
||||||
|
flags: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
panning: float = 0.0
|
||||||
|
surround: float = 0.0
|
||||||
|
"""
|
||||||
|
Chip front/rear balance.
|
||||||
|
"""
|
||||||
|
volume: float = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ModuleMeta:
|
||||||
|
"""
|
||||||
|
Module metadata.
|
||||||
|
"""
|
||||||
|
name: str = ''
|
||||||
|
name_jp: str = ''
|
||||||
|
author: str = ''
|
||||||
|
author_jp: str = ''
|
||||||
|
album: str = ''
|
||||||
|
"""
|
||||||
|
Can also be the game name or container name.
|
||||||
|
"""
|
||||||
|
album_jp: str = ''
|
||||||
|
sys_name: str = 'Sega Genesis/Mega Drive'
|
||||||
|
sys_name_jp: str = ''
|
||||||
|
comment: str = ''
|
||||||
|
version: int = 0
|
||||||
|
tuning: float = 440.0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TimingInfo:
|
||||||
|
"""
|
||||||
|
Timing information for a single subsong.
|
||||||
|
"""
|
||||||
|
arp_speed = 1
|
||||||
|
clock_speed = 60.0
|
||||||
|
highlight: Tuple[int, int] = (4, 16)
|
||||||
|
speed: Tuple[int, int] = (0, 0)
|
||||||
|
timebase = 1
|
||||||
|
virtual_tempo: Tuple[int, int] = (150, 150)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ChipList:
|
||||||
|
"""
|
||||||
|
Information about chips used in the module.
|
||||||
|
"""
|
||||||
|
list: List[ChipInfo] = field(default_factory=list)
|
||||||
|
master_volume: float = 2.0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(repr=False)
|
||||||
|
class ChannelDisplayInfo:
|
||||||
|
"""
|
||||||
|
Relating to channel display in Pattern and Order windows.
|
||||||
|
"""
|
||||||
|
name: str = ''
|
||||||
|
abbreviation: str = ''
|
||||||
|
collapsed: bool = False
|
||||||
|
shown: bool = True
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "ChannelDisplayInfo(name='%s', abbreviation='%s', collapsed=%s, shown=%s)" % (
|
||||||
|
self.name,
|
||||||
|
self.abbreviation,
|
||||||
|
self.collapsed,
|
||||||
|
self.shown
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ModuleCompatFlags:
|
||||||
|
"""
|
||||||
|
Module compatibility flags, a.k.a. "The Motherload"
|
||||||
|
|
||||||
|
Default values correspond with fileOps.cpp in the furnace src.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# compat 1
|
||||||
|
|
||||||
|
limit_slides: bool = False
|
||||||
|
linear_pitch: LinearPitch = field(default_factory=lambda: LinearPitch.FULL_LINEAR)
|
||||||
|
loop_modality: LoopModality = field(default_factory=lambda: LoopModality.DO_NOTHING)
|
||||||
|
proper_noise_layout: bool = True
|
||||||
|
wave_duty_is_volume: bool = False
|
||||||
|
reset_macro_on_porta: bool = False
|
||||||
|
legacy_volume_slides: bool = False
|
||||||
|
compatible_arpeggio: bool = False
|
||||||
|
note_off_resets_slides: bool = True
|
||||||
|
target_resets_slides: bool = True
|
||||||
|
arpeggio_inhibits_portamento: bool = False
|
||||||
|
wack_algorithm_macro: bool = False
|
||||||
|
broken_shortcut_slides: bool = False
|
||||||
|
ignore_duplicates_slides: bool = False
|
||||||
|
stop_portamento_on_note_off: bool = False
|
||||||
|
continuous_vibrato: bool = False
|
||||||
|
broken_dac_mode: bool = False
|
||||||
|
one_tick_cut: bool = False
|
||||||
|
instrument_change_allowed_in_porta: bool = True
|
||||||
|
reset_note_base_on_arpeggio_stop: bool = True
|
||||||
|
|
||||||
|
# compat 2 (>= dev70)
|
||||||
|
|
||||||
|
broken_speed_selection: bool = False
|
||||||
|
no_slides_on_first_tick: bool = False
|
||||||
|
next_row_reset_arp_pos: bool = False
|
||||||
|
ignore_jump_at_end: bool = False
|
||||||
|
buggy_portamento_after_slide: bool = False
|
||||||
|
gb_ins_affects_env: bool = True
|
||||||
|
shared_extch_state: bool = True
|
||||||
|
ignore_outside_dac_mode_change: bool = False
|
||||||
|
e1e2_takes_priority: bool = False
|
||||||
|
new_sega_pcm: bool = True
|
||||||
|
weird_fnum_pitch_slides: bool = False
|
||||||
|
sn_duty_resets_phase: bool = False
|
||||||
|
linear_pitch_macro: bool = True
|
||||||
|
pitch_slide_speed_in_linear: int = 4
|
||||||
|
old_octave_boundary: bool = False
|
||||||
|
disable_opn2_dac_volume_control: bool = False
|
||||||
|
new_volume_scaling: bool = True
|
||||||
|
volume_macro_lingers: bool = True
|
||||||
|
broken_out_vol: bool = False
|
||||||
|
e1e2_stop_on_same_note: bool = False
|
||||||
|
broken_porta_after_arp: bool = False
|
||||||
|
sn_no_low_periods: bool = False
|
||||||
|
cut_delay_effect_policy: DelayBehavior = field(default_factory=lambda: DelayBehavior.LAX)
|
||||||
|
jump_treatment: JumpTreatment = field(default_factory=lambda: JumpTreatment.ALL_JUMPS)
|
||||||
|
auto_sys_name: bool = True
|
||||||
|
disable_sample_macro: bool = False
|
||||||
|
broken_out_vol_2: bool = False
|
||||||
|
old_arp_strategy: bool = False
|
||||||
|
|
||||||
|
# not-a-compat (>= dev135)
|
||||||
|
|
||||||
|
auto_patchbay: bool = True
|
||||||
|
|
||||||
|
# compat 3 (>= dev138)
|
||||||
|
|
||||||
|
broken_porta_during_legato: bool = False
|
||||||
|
|
||||||
|
broken_fm_off: bool = False
|
||||||
|
pre_note_no_effect: bool = False
|
||||||
|
old_dpcm: bool = False
|
||||||
|
reset_arp_phase_on_new_note: bool = False
|
||||||
|
ceil_volume_scaling: bool = False
|
||||||
|
old_always_set_volume: bool = False
|
||||||
|
old_sample_offset: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SubSong:
|
||||||
|
"""
|
||||||
|
Information on a single subsong.
|
||||||
|
"""
|
||||||
|
name: str = ''
|
||||||
|
comment: str = ''
|
||||||
|
speed_pattern: List[int] = field(default_factory=lambda: [6])
|
||||||
|
"""
|
||||||
|
Maximum 16 entries.
|
||||||
|
"""
|
||||||
|
grooves: List[List[int]] = field(default_factory=list)
|
||||||
|
timing: TimingInfo = field(default_factory=TimingInfo)
|
||||||
|
pattern_length = 64
|
||||||
|
order: Dict[int, List[int]] = field(default_factory=lambda: {
|
||||||
|
0: [0], 1: [0], 2: [0], 3: [0], 4: [0],
|
||||||
|
5: [0], 6: [0], 7: [0], 8: [0], 9: [0]
|
||||||
|
})
|
||||||
|
effect_columns: List[int] = field(default_factory=lambda: [
|
||||||
|
1 for _ in range(
|
||||||
|
ChipType.YM2612.channels + ChipType.SMS.channels
|
||||||
|
)
|
||||||
|
])
|
||||||
|
channel_display: List[ChannelDisplayInfo] = field(default_factory=lambda: [
|
||||||
|
ChannelDisplayInfo() for _ in range(
|
||||||
|
ChipType.YM2612.channels + ChipType.SMS.channels
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FurnaceRow:
|
||||||
|
"""
|
||||||
|
Represents a single row in a pattern.
|
||||||
|
"""
|
||||||
|
note: Note
|
||||||
|
octave: int
|
||||||
|
instrument: int
|
||||||
|
volume: int
|
||||||
|
effects: List[Tuple[int, int]] = field(default_factory=list)
|
||||||
|
|
||||||
|
def as_clipboard(self) -> str:
|
||||||
|
"""
|
||||||
|
Renders the selected row in Furnace clipboard format (without header!)
|
||||||
|
|
||||||
|
:return: Furnace clipboard data (str)
|
||||||
|
"""
|
||||||
|
note_maps = {
|
||||||
|
Note.Cs: "C#",
|
||||||
|
Note.D_: "D-",
|
||||||
|
Note.Ds: "D#",
|
||||||
|
Note.E_: "E-",
|
||||||
|
Note.F_: "F-",
|
||||||
|
Note.Fs: "F#",
|
||||||
|
Note.G_: "G-",
|
||||||
|
Note.Gs: "G#",
|
||||||
|
Note.A_: "A-",
|
||||||
|
Note.As: "A#",
|
||||||
|
Note.B_: "B-",
|
||||||
|
Note.C_: "C-",
|
||||||
|
}
|
||||||
|
if self.note == Note.OFF:
|
||||||
|
note_str = "OFF"
|
||||||
|
elif self.note == Note.OFF_REL:
|
||||||
|
note_str = "==="
|
||||||
|
elif self.note == Note.REL:
|
||||||
|
note_str = "REL"
|
||||||
|
elif self.note == Note.__:
|
||||||
|
note_str = "..."
|
||||||
|
else:
|
||||||
|
note_str = "%s%d" % (note_maps[self.note], self.octave)
|
||||||
|
|
||||||
|
vol = ".." if self.volume==0xffff else "%02X" % self.volume
|
||||||
|
ins = ".." if self.instrument==0xffff else "%02X" % self.instrument
|
||||||
|
|
||||||
|
rep_str = "%s%s%s"
|
||||||
|
|
||||||
|
for fx in self.effects:
|
||||||
|
cmd, val = fx
|
||||||
|
cmd_str = ".." if cmd == 0xffff else "%02X" % cmd
|
||||||
|
val_str = ".." if val == 0xffff else "%02X" % val
|
||||||
|
rep_str += "%s%s" % (cmd_str, val_str)
|
||||||
|
|
||||||
|
return rep_str % (
|
||||||
|
note_str,
|
||||||
|
ins, vol
|
||||||
|
) + "|"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if self.note == Note.OFF:
|
||||||
|
note_str = "OFF"
|
||||||
|
elif self.note == Note.OFF_REL:
|
||||||
|
note_str = "==="
|
||||||
|
elif self.note == Note.REL:
|
||||||
|
note_str = "///"
|
||||||
|
elif self.note == Note.__:
|
||||||
|
note_str = "---"
|
||||||
|
else:
|
||||||
|
note_str = "%s%d" % (self.note, self.octave)
|
||||||
|
|
||||||
|
vol = "--" if self.volume==0xffff else "%02x" % self.volume
|
||||||
|
ins = "--" if self.instrument==0xffff else "%02x" % self.instrument
|
||||||
|
|
||||||
|
rep_str = "row data: %s %s %s"
|
||||||
|
|
||||||
|
for fx in self.effects:
|
||||||
|
cmd, val = fx
|
||||||
|
cmd_str = "--" if cmd == 0xffff else "%02x" % cmd
|
||||||
|
val_str = "--" if val == 0xffff else "%02x" % val
|
||||||
|
rep_str += " %s%s" % (cmd_str, val_str)
|
||||||
|
|
||||||
|
return "<" + rep_str % (
|
||||||
|
note_str,
|
||||||
|
ins, vol
|
||||||
|
) + ">"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FurnacePattern:
|
||||||
|
"""
|
||||||
|
Represents one pattern in a module.
|
||||||
|
"""
|
||||||
|
channel: int = 0
|
||||||
|
index: int = 0
|
||||||
|
subsong: int = 0
|
||||||
|
data: List[FurnaceRow] = field(default_factory=list) # yeah...
|
||||||
|
name: str = ""
|
||||||
|
|
||||||
|
def as_clipboard(self) -> str:
|
||||||
|
"""
|
||||||
|
Renders the selected pattern in Furnace clipboard format.
|
||||||
|
|
||||||
|
:return: Furnace clipboard data
|
||||||
|
"""
|
||||||
|
return "org.tildearrow.furnace - Pattern Data\n0\n" + "\n".join([x.as_clipboard() for x in self.data])
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return "<Furnace pattern %s for ch.%02d of subsong %02d>" % (
|
||||||
|
self.name if len(self.name) > 0 else "%02x" % self.index,
|
||||||
|
self.channel,
|
||||||
|
self.subsong
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InputPatchBayEntry(TypedDict):
|
||||||
|
"""
|
||||||
|
A patch that has an "input" connector.
|
||||||
|
"""
|
||||||
|
set: InputPortSet
|
||||||
|
"""
|
||||||
|
The set that the patch belongs to.
|
||||||
|
"""
|
||||||
|
port: int
|
||||||
|
"""
|
||||||
|
Which port to connect to.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class OutputPatchBayEntry(TypedDict):
|
||||||
|
"""
|
||||||
|
A patch that has an "output" connector.
|
||||||
|
"""
|
||||||
|
set: OutputPortSet
|
||||||
|
"""
|
||||||
|
The set that the patch belongs to.
|
||||||
|
"""
|
||||||
|
port: int
|
||||||
|
"""
|
||||||
|
Which port to connect from.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PatchBay:
|
||||||
|
"""
|
||||||
|
A single patchbay connection.
|
||||||
|
"""
|
||||||
|
source: OutputPatchBayEntry
|
||||||
|
dest: InputPatchBayEntry
|
||||||
|
|
||||||
|
|
||||||
|
# instruments
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureAbstract:
|
||||||
|
"""
|
||||||
|
Base class for all InsFeature* classes. Not really to be used.
|
||||||
|
"""
|
||||||
|
_code: str = field(init=False)
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
if len(self._code) != 2:
|
||||||
|
raise ValueError('No code defined for this instrument feature')
|
||||||
|
|
||||||
|
# def serialize(self) -> bytes:
|
||||||
|
# raise Exception('Method serialize() has not been overridden...')
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureName(InsFeatureAbstract, str):
|
||||||
|
"""
|
||||||
|
Instrument's name block. Can be used as a string.
|
||||||
|
"""
|
||||||
|
_code = 'NA'
|
||||||
|
name: str = ''
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsMeta:
|
||||||
|
version: int = 143
|
||||||
|
type: InstrumentType = InstrumentType.FM_4OP
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFMOperator:
|
||||||
|
am: bool = False
|
||||||
|
ar: int = 0
|
||||||
|
dr: int = 0
|
||||||
|
mult: int = 0
|
||||||
|
rr: int = 0
|
||||||
|
sl: int = 0
|
||||||
|
tl: int = 0
|
||||||
|
dt2: int = 0
|
||||||
|
rs: int = 0
|
||||||
|
dt: int = 0
|
||||||
|
d2r: int = 0
|
||||||
|
ssg_env: int = 0
|
||||||
|
dam: int = 0
|
||||||
|
dvb: int = 0
|
||||||
|
egt: bool = False
|
||||||
|
ksl: int = 0
|
||||||
|
sus: bool = False
|
||||||
|
vib: bool = False
|
||||||
|
ws: int = 0
|
||||||
|
ksr: bool = False
|
||||||
|
enable: bool = True
|
||||||
|
kvs: int = 2
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureFM(InsFeatureAbstract):
|
||||||
|
_code = 'FM'
|
||||||
|
alg: int = 0
|
||||||
|
fb: int = 4
|
||||||
|
fms: int = 0
|
||||||
|
ams: int = 0
|
||||||
|
fms2: int = 0
|
||||||
|
ams2: int = 0
|
||||||
|
ops: int = 2
|
||||||
|
opll_preset: int = 0
|
||||||
|
op_list: List[InsFMOperator] = field(default_factory=lambda: [
|
||||||
|
InsFMOperator(
|
||||||
|
tl=42, ar=31, dr=8,
|
||||||
|
sl=15, rr=3, mult=5,
|
||||||
|
dt=5
|
||||||
|
),
|
||||||
|
InsFMOperator(
|
||||||
|
tl=48, ar=31, dr=4,
|
||||||
|
sl=11, rr=1, mult=1,
|
||||||
|
dt=5
|
||||||
|
),
|
||||||
|
InsFMOperator(
|
||||||
|
tl=18, ar=31, dr=10,
|
||||||
|
sl=15, rr=4, mult=1,
|
||||||
|
dt=0
|
||||||
|
),
|
||||||
|
InsFMOperator(
|
||||||
|
tl=2, ar=31, dr=9,
|
||||||
|
sl=15, rr=9, mult=1,
|
||||||
|
dt=0
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SingleMacro:
|
||||||
|
kind: Union[MacroCode, OpMacroCode] = field(default_factory=lambda: MacroCode.VOL)
|
||||||
|
mode: int = 0
|
||||||
|
type: MacroType = field(default_factory=lambda: MacroType.SEQUENCE)
|
||||||
|
delay: int = 0
|
||||||
|
speed: int = 1
|
||||||
|
open: bool = False
|
||||||
|
data: List[Union[int, MacroItem]] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureMacro(InsFeatureAbstract):
|
||||||
|
_code = 'MA'
|
||||||
|
macros: List[SingleMacro] = field(default_factory=lambda: [SingleMacro()])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureOpr1Macro(InsFeatureMacro):
|
||||||
|
_code = 'O1'
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureOpr2Macro(InsFeatureMacro):
|
||||||
|
_code = 'O2'
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureOpr3Macro(InsFeatureMacro):
|
||||||
|
_code = 'O3'
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureOpr4Macro(InsFeatureMacro):
|
||||||
|
_code = 'O4'
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GBHwSeq:
|
||||||
|
command: GBHwCommand
|
||||||
|
data: List[int] = field(default_factory=lambda: [0, 0])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureGB(InsFeatureAbstract):
|
||||||
|
_code = 'GB'
|
||||||
|
env_vol: int = 15
|
||||||
|
env_dir: int = 0
|
||||||
|
env_len: int = 2
|
||||||
|
sound_len: int = 0
|
||||||
|
soft_env: bool = False
|
||||||
|
always_init: bool = False
|
||||||
|
hw_seq: List[GBHwSeq] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GenericADSR:
|
||||||
|
a: int = 0
|
||||||
|
d: int = 0
|
||||||
|
s: int = 0
|
||||||
|
r: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureC64(InsFeatureAbstract):
|
||||||
|
_code = '64'
|
||||||
|
tri_on: bool = False
|
||||||
|
saw_on: bool = True
|
||||||
|
pulse_on: bool = False
|
||||||
|
noise_on: bool = False
|
||||||
|
envelope: GenericADSR = field(default_factory=lambda: GenericADSR(a=0, d=8, s=0, r=0))
|
||||||
|
duty: int = 2048
|
||||||
|
ring_mod: int = 0
|
||||||
|
osc_sync: int = 0
|
||||||
|
to_filter: bool = False
|
||||||
|
vol_is_cutoff: bool = False
|
||||||
|
init_filter: bool = False
|
||||||
|
duty_is_abs: bool = False
|
||||||
|
filter_is_abs: bool = False
|
||||||
|
no_test: bool = False
|
||||||
|
res: int = 0
|
||||||
|
cut: int = 0
|
||||||
|
hp: bool = False
|
||||||
|
lp: bool = False
|
||||||
|
bp: bool = False
|
||||||
|
ch3_off: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SampleMap:
|
||||||
|
freq: int = 0
|
||||||
|
sample_index: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DPCMMap:
|
||||||
|
pitch: int = 0
|
||||||
|
delta: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureAmiga(InsFeatureAbstract): # Sample data
|
||||||
|
_code = 'SM'
|
||||||
|
init_sample: int = 0
|
||||||
|
use_note_map: bool = False
|
||||||
|
use_sample: bool = False
|
||||||
|
use_wave: bool = False
|
||||||
|
wave_len: int = 31
|
||||||
|
sample_map: List[SampleMap] = field(default_factory=lambda: [SampleMap() for _ in range(120)])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureDPCMMap(InsFeatureAbstract): # DPCM sample data
|
||||||
|
_code = 'NE'
|
||||||
|
use_map: bool = False
|
||||||
|
sample_map: List[DPCMMap] = field(default_factory=lambda: [SampleMap() for _ in range(120)])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureX1010(InsFeatureAbstract):
|
||||||
|
_code = 'X1'
|
||||||
|
bank_slot: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeaturePowerNoise(InsFeatureAbstract):
|
||||||
|
_code = 'PN'
|
||||||
|
octave: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureSID2(InsFeatureAbstract):
|
||||||
|
_code = 'S2'
|
||||||
|
noise_mode: int = 0
|
||||||
|
wave_mix: int = 0
|
||||||
|
volume: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureN163(InsFeatureAbstract):
|
||||||
|
_code = 'N1'
|
||||||
|
wave: int = -1
|
||||||
|
wave_pos: int = 0
|
||||||
|
wave_len: int = 32
|
||||||
|
wave_mode: int = 3
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureFDS(InsFeatureAbstract): # Virtual Boy
|
||||||
|
_code = 'FD'
|
||||||
|
mod_speed: int = 0
|
||||||
|
mod_depth: int = 0
|
||||||
|
init_table_with_first_wave: bool = False # compat
|
||||||
|
mod_table: List[int] = field(default_factory=lambda: [0 for i in range(32)])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureMultiPCM(InsFeatureAbstract):
|
||||||
|
_code = 'MP'
|
||||||
|
ar: int = 15
|
||||||
|
d1r: int = 15
|
||||||
|
dl: int = 0
|
||||||
|
d2r: int = 0
|
||||||
|
rr: int = 15
|
||||||
|
rc: int = 15
|
||||||
|
lfo: int = 0
|
||||||
|
vib: int = 0
|
||||||
|
am: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureWaveSynth(InsFeatureAbstract):
|
||||||
|
_code = 'WS'
|
||||||
|
wave_indices: List[int] = field(default_factory=lambda: [0, 0])
|
||||||
|
rate_divider: int = 1
|
||||||
|
effect: WaveFX = WaveFX.NONE
|
||||||
|
enabled: bool = False
|
||||||
|
global_effect: bool = False
|
||||||
|
speed: int = 0
|
||||||
|
params: List[int] = field(default_factory=lambda: [0, 0, 0, 0])
|
||||||
|
one_shot: bool = False # not read?
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureSoundUnit(InsFeatureAbstract):
|
||||||
|
_code = 'SU'
|
||||||
|
switch_roles: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureES5506(InsFeatureAbstract):
|
||||||
|
_code = 'ES'
|
||||||
|
filter_mode: ESFilterMode = ESFilterMode.LPK2_LPK1
|
||||||
|
k1: int = 0xffff
|
||||||
|
k2: int = 0xffff
|
||||||
|
env_count: int = 0
|
||||||
|
left_volume_ramp: int = 0
|
||||||
|
right_volume_ramp: int = 0
|
||||||
|
k1_ramp: int = 0
|
||||||
|
k2_ramp: int = 0
|
||||||
|
k1_slow: int = 0
|
||||||
|
k2_slow: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureSNES(InsFeatureAbstract):
|
||||||
|
_code = 'SN'
|
||||||
|
use_env: bool = True
|
||||||
|
sus: SNESSusMode = SNESSusMode.DIRECT
|
||||||
|
gain_mode: GainMode = GainMode.DIRECT
|
||||||
|
gain: int = 127
|
||||||
|
d2: int = 0
|
||||||
|
envelope: GenericADSR = field(default_factory=lambda: GenericADSR(a=15, d=7, s=7, r=0))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureOPLDrums(InsFeatureAbstract):
|
||||||
|
_code = 'LD'
|
||||||
|
fixed_drums: bool = False
|
||||||
|
kick_freq: int = 1312
|
||||||
|
snare_hat_freq: int = 1360
|
||||||
|
tom_top_freq: int = 448
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _InsFeaturePointerAbstract(InsFeatureAbstract):
|
||||||
|
"""
|
||||||
|
Also not really to be used. Container for all "list" features.
|
||||||
|
"""
|
||||||
|
_code = 'LL'
|
||||||
|
pointers: Dict[int, int] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureSampleList(_InsFeaturePointerAbstract):
|
||||||
|
"""
|
||||||
|
List of pointers to all samples used by this instrument.
|
||||||
|
"""
|
||||||
|
_code = 'SL'
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InsFeatureWaveList(_InsFeaturePointerAbstract):
|
||||||
|
"""
|
||||||
|
List of pointers to all wave tables used by this instrument.
|
||||||
|
"""
|
||||||
|
_code = 'WL'
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WavetableMeta:
|
||||||
|
name: str = ''
|
||||||
|
width: int = 32
|
||||||
|
height: int = 32
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SampleMeta:
|
||||||
|
name: str = ''
|
||||||
|
length: int = 0
|
||||||
|
sample_rate: int = 0
|
||||||
|
bitdepth: int = 0
|
||||||
|
loop_start: int = 0
|
||||||
|
loop_end: int = 0
|
||||||
648
loader/samples/minexample/furC64/chipchune/furnace/enums.py
Normal file
648
loader/samples/minexample/furC64/chipchune/furnace/enums.py
Normal file
|
|
@ -0,0 +1,648 @@
|
||||||
|
from chipchune._util import EnumShowNameOnly, EnumValueEquals
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
class LinearPitch(EnumShowNameOnly, EnumValueEquals):
|
||||||
|
"""
|
||||||
|
Options for :attr:`chipchune.furnace.data_types.ModuleCompatFlags.linear_pitch`.
|
||||||
|
"""
|
||||||
|
NON_LINEAR = 0
|
||||||
|
ONLY_PITCH_CHANGE = 1
|
||||||
|
FULL_LINEAR = 2
|
||||||
|
|
||||||
|
|
||||||
|
class LoopModality(EnumShowNameOnly, EnumValueEquals):
|
||||||
|
"""
|
||||||
|
Options for :attr:`chipchune.furnace.data_types.ModuleCompatFlags.loop_modality`.
|
||||||
|
"""
|
||||||
|
HARD_RESET_CHANNELS = 0
|
||||||
|
SOFT_RESET_CHANNELS = 1
|
||||||
|
DO_NOTHING = 2
|
||||||
|
|
||||||
|
|
||||||
|
class DelayBehavior(EnumShowNameOnly, EnumValueEquals):
|
||||||
|
"""
|
||||||
|
Options for :attr:`chipchune.furnace.data_types.ModuleCompatFlags.cut_delay_effect_policy`.
|
||||||
|
"""
|
||||||
|
STRICT = 0
|
||||||
|
BROKEN = 1
|
||||||
|
LAX = 2
|
||||||
|
|
||||||
|
|
||||||
|
class JumpTreatment(EnumShowNameOnly, EnumValueEquals):
|
||||||
|
"""
|
||||||
|
Options for :attr:`chipchune.furnace.data_types.ModuleCompatFlags.jump_treatment`.
|
||||||
|
"""
|
||||||
|
ALL_JUMPS = 0
|
||||||
|
FIRST_JUMP_ONLY = 1
|
||||||
|
ROW_JUMP_PRIORITY = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Note(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
All notes recognized by Furnace
|
||||||
|
"""
|
||||||
|
__ = 0
|
||||||
|
Cs = 1
|
||||||
|
D_ = 2
|
||||||
|
Ds = 3
|
||||||
|
E_ = 4
|
||||||
|
F_ = 5
|
||||||
|
Fs = 6
|
||||||
|
G_ = 7
|
||||||
|
Gs = 8
|
||||||
|
A_ = 9
|
||||||
|
As = 10
|
||||||
|
B_ = 11
|
||||||
|
C_ = 12
|
||||||
|
OFF = 100
|
||||||
|
OFF_REL = 101
|
||||||
|
REL = 102
|
||||||
|
|
||||||
|
|
||||||
|
class MacroItem(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Special values used only in this parser, to allow data editing similar to that
|
||||||
|
of Furnace itself.
|
||||||
|
"""
|
||||||
|
LOOP = 0
|
||||||
|
RELEASE = 1
|
||||||
|
|
||||||
|
|
||||||
|
class MacroCode(EnumShowNameOnly, EnumValueEquals):
|
||||||
|
"""
|
||||||
|
Marks what aspect of an instrument does a macro change.
|
||||||
|
"""
|
||||||
|
|
||||||
|
VOL = 0
|
||||||
|
"""
|
||||||
|
Also:
|
||||||
|
- C64 cutoff
|
||||||
|
"""
|
||||||
|
|
||||||
|
ARP = 1
|
||||||
|
"""
|
||||||
|
Not applicable to MSM6258 and MSM6295.
|
||||||
|
"""
|
||||||
|
|
||||||
|
DUTY = 2
|
||||||
|
"""
|
||||||
|
Also:
|
||||||
|
- AY noise freq
|
||||||
|
- POKEY audctl
|
||||||
|
- Mikey duty/int
|
||||||
|
- MSM5232 group ctrl
|
||||||
|
- Beeper/Pokemon Mini pulse width
|
||||||
|
- T6W28 noise type
|
||||||
|
- Virtual Boy noise length
|
||||||
|
- PC Engine/Namco/WonderSwan noise type
|
||||||
|
- SNES noise freq
|
||||||
|
- Namco 163 waveform pos.
|
||||||
|
- ES5506 filter mode
|
||||||
|
- MSM6258/MSM6295 freq. divider
|
||||||
|
- ADPCMA global volume
|
||||||
|
- QSound echo level
|
||||||
|
"""
|
||||||
|
|
||||||
|
WAVE = 3
|
||||||
|
"""
|
||||||
|
Also:
|
||||||
|
- OPLL patch
|
||||||
|
- OPZ/OPM lfo1 shape
|
||||||
|
"""
|
||||||
|
|
||||||
|
PITCH = 4
|
||||||
|
|
||||||
|
EX1 = 5
|
||||||
|
"""
|
||||||
|
- OPZ/OPM am depth
|
||||||
|
- C64 filter mode
|
||||||
|
- SAA1099 envelope
|
||||||
|
- X1-010 env. mode
|
||||||
|
- Namco 163 wave length
|
||||||
|
- FDS mod depth
|
||||||
|
- TSU cutoff
|
||||||
|
- ES5506 filter k1
|
||||||
|
- MSM6258 clk divider
|
||||||
|
- QSound echo feedback
|
||||||
|
- SNES special
|
||||||
|
- MSM5232 group attack
|
||||||
|
- AY8930 duty?
|
||||||
|
"""
|
||||||
|
|
||||||
|
EX2 = 6
|
||||||
|
"""
|
||||||
|
- C64 resonance
|
||||||
|
- Namco 163 wave update
|
||||||
|
- FDS mod speed
|
||||||
|
- TSU resonance
|
||||||
|
- ES5506 filter k2
|
||||||
|
- QSound echo length
|
||||||
|
- SNES gain
|
||||||
|
- MSM5232 group decay
|
||||||
|
- AY3/AY8930 envelope
|
||||||
|
"""
|
||||||
|
|
||||||
|
EX3 = 7
|
||||||
|
"""
|
||||||
|
- C64 special
|
||||||
|
- AY/AY8930 autoenv num
|
||||||
|
- X1-010 autoenv num
|
||||||
|
- Namco 163 waveload wave
|
||||||
|
- FDS mod position
|
||||||
|
- TSU control
|
||||||
|
- MSM5232 noise
|
||||||
|
"""
|
||||||
|
|
||||||
|
ALG = 8
|
||||||
|
"""
|
||||||
|
Also:
|
||||||
|
- AY/AY8930 autoenv den
|
||||||
|
- X1-010 autoenv den
|
||||||
|
- Namco 163 waveload pos
|
||||||
|
- ES5506 control
|
||||||
|
"""
|
||||||
|
|
||||||
|
FB = 9
|
||||||
|
"""
|
||||||
|
Also:
|
||||||
|
- AY8930 noise & mask
|
||||||
|
- Namco 163 waveload len
|
||||||
|
- ES5506 outputs
|
||||||
|
"""
|
||||||
|
|
||||||
|
FMS = 10
|
||||||
|
"""
|
||||||
|
Also:
|
||||||
|
- AY8930 noise | mask
|
||||||
|
- Namco 163 waveload trigger
|
||||||
|
"""
|
||||||
|
|
||||||
|
AMS = 11
|
||||||
|
|
||||||
|
PAN_L = 12
|
||||||
|
|
||||||
|
PAN_R = 13
|
||||||
|
|
||||||
|
PHASE_RESET = 14
|
||||||
|
|
||||||
|
EX4 = 15
|
||||||
|
"""
|
||||||
|
- C64 test/gate
|
||||||
|
- TSU phase reset timer
|
||||||
|
- FM/OPM opmask
|
||||||
|
"""
|
||||||
|
|
||||||
|
EX5 = 16
|
||||||
|
"""
|
||||||
|
- OPZ am depth 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
EX6 = 17
|
||||||
|
"""
|
||||||
|
- OPZ pm depth 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
EX7 = 18
|
||||||
|
"""
|
||||||
|
- OPZ lfo2 speed
|
||||||
|
"""
|
||||||
|
|
||||||
|
EX8 = 19
|
||||||
|
"""
|
||||||
|
- OPZ lfo2 shape
|
||||||
|
"""
|
||||||
|
|
||||||
|
STOP = 255
|
||||||
|
"""
|
||||||
|
Marks end of macro reading.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class OpMacroCode(EnumShowNameOnly, EnumValueEquals):
|
||||||
|
"""
|
||||||
|
Controls which FM parameter a macro should change.
|
||||||
|
"""
|
||||||
|
AM = 0
|
||||||
|
AR = 1
|
||||||
|
DR = 2
|
||||||
|
MULT = 3
|
||||||
|
RR = 4
|
||||||
|
SL = 5
|
||||||
|
TL = 6
|
||||||
|
DT2 = 7
|
||||||
|
RS = 8
|
||||||
|
DT = 9
|
||||||
|
D2R = 10
|
||||||
|
SSG_EG = 11
|
||||||
|
DAM = 12
|
||||||
|
DVB = 13
|
||||||
|
EGT = 14
|
||||||
|
KSL = 15
|
||||||
|
SUS = 16
|
||||||
|
VIB = 17
|
||||||
|
WS = 18
|
||||||
|
KSR = 19
|
||||||
|
|
||||||
|
|
||||||
|
class MacroType(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Instrument macro type (version 120+).
|
||||||
|
"""
|
||||||
|
SEQUENCE = 0
|
||||||
|
ADSR = 1
|
||||||
|
LFO = 2
|
||||||
|
|
||||||
|
|
||||||
|
class MacroSize(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Type of value stored in the instrument file.
|
||||||
|
"""
|
||||||
|
_value_: int
|
||||||
|
num_bytes: int
|
||||||
|
signed: bool
|
||||||
|
|
||||||
|
UINT8: Tuple[int, int, bool] = (0, 1, False)
|
||||||
|
INT8: Tuple[int, int, bool] = (1, 1, True)
|
||||||
|
INT16: Tuple[int, int, bool] = (2, 2, True)
|
||||||
|
INT32: Tuple[int, int, bool] = (3, 4, True)
|
||||||
|
|
||||||
|
def __new__(cls, id: int, num_bytes: int, signed: bool): # type: ignore[no-untyped-def]
|
||||||
|
member = object.__new__(cls)
|
||||||
|
member._value_ = id
|
||||||
|
setattr(member, 'num_bytes', num_bytes)
|
||||||
|
setattr(member, 'signed', signed)
|
||||||
|
return member
|
||||||
|
|
||||||
|
|
||||||
|
class GBHwCommand(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Game Boy hardware envelope commands.
|
||||||
|
"""
|
||||||
|
ENVELOPE = 0
|
||||||
|
SWEEP = 1
|
||||||
|
WAIT = 2
|
||||||
|
WAIT_REL = 3
|
||||||
|
LOOP = 4
|
||||||
|
LOOP_REL = 5
|
||||||
|
|
||||||
|
|
||||||
|
class SampleType(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Sample types used in Furnace
|
||||||
|
"""
|
||||||
|
ZX_DRUM = 0
|
||||||
|
NES_DPCM = 1
|
||||||
|
QSOUND_ADPCM = 4
|
||||||
|
ADPCM_A = 5
|
||||||
|
ADPCM_B = 6
|
||||||
|
X68K_ADPCM = 7
|
||||||
|
PCM_8 = 8
|
||||||
|
SNES_BRR = 9
|
||||||
|
VOX = 10
|
||||||
|
PCM_16 = 16
|
||||||
|
|
||||||
|
|
||||||
|
class InstrumentType(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Instrument types currently available as of version 144.
|
||||||
|
"""
|
||||||
|
STANDARD = 0
|
||||||
|
FM_4OP = 1
|
||||||
|
GB = 2
|
||||||
|
C64 = 3
|
||||||
|
AMIGA = 4
|
||||||
|
PCE = 5
|
||||||
|
SSG = 6
|
||||||
|
AY8930 = 7
|
||||||
|
TIA = 8
|
||||||
|
SAA1099 = 9
|
||||||
|
VIC = 10
|
||||||
|
PET = 11
|
||||||
|
VRC6 = 12
|
||||||
|
FM_OPLL = 13
|
||||||
|
FM_OPL = 14
|
||||||
|
FDS = 15
|
||||||
|
VB = 16
|
||||||
|
N163 = 17
|
||||||
|
KONAMI_SCC = 18
|
||||||
|
FM_OPZ = 19
|
||||||
|
POKEY = 20
|
||||||
|
PC_BEEPER = 21
|
||||||
|
WONDERSWAN = 22
|
||||||
|
LYNX = 23
|
||||||
|
VERA = 24
|
||||||
|
X1010 = 25
|
||||||
|
VRC6_SAW = 26
|
||||||
|
ES5506 = 27
|
||||||
|
MULTIPCM = 28
|
||||||
|
SNES = 29
|
||||||
|
TSU = 30
|
||||||
|
NAMCO_WSG = 31
|
||||||
|
OPL_DRUMS = 32
|
||||||
|
FM_OPM = 33
|
||||||
|
NES = 34
|
||||||
|
MSM6258 = 35
|
||||||
|
MSM6295 = 36
|
||||||
|
ADPCM_A = 37
|
||||||
|
ADPCM_B = 38
|
||||||
|
SEGAPCM = 39
|
||||||
|
QSOUND = 40
|
||||||
|
YMZ280B = 41
|
||||||
|
RF5C68 = 42
|
||||||
|
MSM5232 = 43
|
||||||
|
T6W28 = 44
|
||||||
|
K007232 = 45
|
||||||
|
GA20 = 46
|
||||||
|
POKEMON_MINI = 47
|
||||||
|
SM8521 = 48
|
||||||
|
PV1000 = 49
|
||||||
|
|
||||||
|
|
||||||
|
class ChipType(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Furnace chip database, either planned or implemented.
|
||||||
|
Contains console name, chip ID and number of channels.
|
||||||
|
"""
|
||||||
|
_value_: int
|
||||||
|
channels: int
|
||||||
|
|
||||||
|
YMU759 = (0x01, 17)
|
||||||
|
GENESIS = (0x02, 10) # YM2612 + SN76489
|
||||||
|
SMS = (0x03, 4) # SN76489
|
||||||
|
GB = (0x04, 4) # LR53902
|
||||||
|
PCE = (0x05, 6) # HuC6280
|
||||||
|
NES = (0x06, 5) # RP2A03
|
||||||
|
C64_8580 = (0x07, 3) # SID r8580
|
||||||
|
SEGA_ARCADE = (0x08, 13) # YM2151 + SegaPCM
|
||||||
|
NEO_GEO_CD = (0x09, 13)
|
||||||
|
|
||||||
|
GENESIS_EX = (0x42, 13) # YM2612 + SN76489
|
||||||
|
SMS_JP = (0x43, 13) # SN76489 + YM2413
|
||||||
|
NES_VRC7 = (0x46, 11) # RP2A03 + YM2413
|
||||||
|
C64_6581 = (0x47, 3) # SID r6581
|
||||||
|
NEO_GEO_CD_EX = (0x49, 16)
|
||||||
|
|
||||||
|
AY38910 = (0x80, 3)
|
||||||
|
AMIGA = (0x81, 4) # Paula
|
||||||
|
YM2151 = (0x82, 8) # YM2151
|
||||||
|
YM2612 = (0x83, 6) # YM2612
|
||||||
|
TIA = (0x84, 2)
|
||||||
|
VIC20 = (0x85, 4)
|
||||||
|
PET = (0x86, 1)
|
||||||
|
SNES = (0x87, 8) # SPC700
|
||||||
|
VRC6 = (0x88, 3)
|
||||||
|
OPLL = (0x89, 9) # YM2413
|
||||||
|
FDS = (0x8a, 1)
|
||||||
|
MMC5 = (0x8b, 3)
|
||||||
|
N163 = (0x8c, 8)
|
||||||
|
OPN = (0x8d, 6) # YM2203
|
||||||
|
PC98 = (0x8e, 16) # YM2608
|
||||||
|
OPL = (0x8f, 9) # YM3526
|
||||||
|
|
||||||
|
OPL2 = (0x90, 9) # YM3812
|
||||||
|
OPL3 = (0x91, 18) # YMF262
|
||||||
|
MULTIPCM = (0x92, 24)
|
||||||
|
PC_SPEAKER = (0x93, 1) # Intel 8253
|
||||||
|
POKEY = (0x94, 4)
|
||||||
|
RF5C68 = (0x95, 8)
|
||||||
|
WONDERSWAN = (0x96, 4)
|
||||||
|
SAA1099 = (0x97, 6)
|
||||||
|
OPZ = (0x98, 8)
|
||||||
|
POKEMON_MINI = (0x99, 1)
|
||||||
|
AY8930 = (0x9a, 3)
|
||||||
|
SEGAPCM = (0x9b, 16)
|
||||||
|
VIRTUAL_BOY = (0x9c, 6)
|
||||||
|
VRC7 = (0x9d, 6)
|
||||||
|
YM2610B = (0x9e, 16)
|
||||||
|
ZX_BEEPER = (0x9f, 6) # tildearrow's engine
|
||||||
|
|
||||||
|
YM2612_EX = (0xa0, 9)
|
||||||
|
SCC = (0xa1, 5)
|
||||||
|
OPL_DRUMS = (0xa2, 11)
|
||||||
|
OPL2_DRUMS = (0xa3, 11)
|
||||||
|
OPL3_DRUMS = (0xa4, 20)
|
||||||
|
NEO_GEO = (0xa5, 14)
|
||||||
|
NEO_GEO_EX = (0xa6, 17)
|
||||||
|
OPLL_DRUMS = (0xa7, 11)
|
||||||
|
LYNX = (0xa8, 4)
|
||||||
|
SEGAPCM_DMF = (0xa9, 5)
|
||||||
|
MSM6295 = (0xaa, 4)
|
||||||
|
MSM6258 = (0xab, 1)
|
||||||
|
COMMANDER_X16 = (0xac, 17) # VERA
|
||||||
|
BUBBLE_SYSTEM_WSG = (0xad, 2)
|
||||||
|
OPL4 = (0xae, 42)
|
||||||
|
OPL4_DRUMS = (0xaf, 44)
|
||||||
|
|
||||||
|
SETA = (0xb0, 16) # Allumer X1-010
|
||||||
|
ES5506 = (0xb1, 32)
|
||||||
|
Y8950 = (0xb2, 10)
|
||||||
|
Y8950_DRUMS = (0xb3, 12)
|
||||||
|
SCC_PLUS = (0xb4, 5)
|
||||||
|
TSU = (0xb5, 8)
|
||||||
|
YM2203_EX = (0xb6, 9)
|
||||||
|
YM2608_EX = (0xb7, 19)
|
||||||
|
YMZ280B = (0xb8, 8)
|
||||||
|
NAMCO = (0xb9, 3) # Namco WSG
|
||||||
|
N15XX = (0xba, 8) # Namco 15xx
|
||||||
|
CUS30 = (0xbb, 8) # Namco CUS30
|
||||||
|
MSM5232 = (0xbc, 8)
|
||||||
|
YM2612_PLUS_EX = (0xbd, 11)
|
||||||
|
YM2612_PLUS = (0xbe, 7)
|
||||||
|
T6W28 = (0xbf, 4)
|
||||||
|
|
||||||
|
PCM_DAC = (0xc0, 1)
|
||||||
|
YM2612_CSM = (0xc1, 10)
|
||||||
|
NEO_GEO_CSM = (0xc2, 18) # YM2610 CSM
|
||||||
|
YM2203_CSM = (0xc3, 10)
|
||||||
|
YM2608_CSM = (0xc4, 20)
|
||||||
|
YM2610B_CSM = (0xc5, 20)
|
||||||
|
K007232 = (0xc6, 2)
|
||||||
|
GA20 = (0xc7, 4)
|
||||||
|
SM8521 = (0xc8, 3)
|
||||||
|
M114S = (0xc9, 16)
|
||||||
|
ZX_BEEPER_QUADTONE: Tuple[int, int] = (0xca, 5) # Natt Akuma's engine
|
||||||
|
PV_1000: Tuple[int, int] = (0xcb, 3) # NEC D65010G031
|
||||||
|
K053260 = (0xcc, 4)
|
||||||
|
TED = (0xcd, 2)
|
||||||
|
NAMCO_C140 = (0xce, 24)
|
||||||
|
NAMCO_C219 = (0xcf, 16)
|
||||||
|
|
||||||
|
NAMCO_C352 = (0xd0, 32)
|
||||||
|
ESFM = (0xd1, 18)
|
||||||
|
ES5503 = (0xd2, 32)
|
||||||
|
POWERNOISE = (0xd4, 4)
|
||||||
|
DAVE = (0xd5, 6)
|
||||||
|
NDS = (0xd6, 16)
|
||||||
|
GBA = (0xd7, 2)
|
||||||
|
GBA_MINMOD = (0xd8, 16)
|
||||||
|
BIFURCATOR = (0xd9, 4)
|
||||||
|
YM2610B_EX = (0xde, 19)
|
||||||
|
|
||||||
|
QSOUND = (0xe0, 19)
|
||||||
|
|
||||||
|
SID2 = (0xf0, 3) # SID2
|
||||||
|
FIVEE01 = (0xf1, 5) # 5E01
|
||||||
|
PONG = (0xfc, 1)
|
||||||
|
DUMMY = (0xfd, 1)
|
||||||
|
|
||||||
|
RESERVED_1 = (0xfe, 1)
|
||||||
|
RESERVED_2 = (0xff, 1)
|
||||||
|
|
||||||
|
def __new__(cls, id: int, channels: int): # type: ignore[no-untyped-def]
|
||||||
|
member = object.__new__(cls)
|
||||||
|
member._value_ = id
|
||||||
|
setattr(member, 'channels', channels)
|
||||||
|
return member
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
# repr abuse
|
||||||
|
# about as stupid as "mapping for the renderer"...
|
||||||
|
return "%s (0x%02x), %d channel%s" % (
|
||||||
|
self.name, self._value_, self.channels,
|
||||||
|
"s" if self.channels != 1 else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InputPortSet(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Devices which contain an "input" port.
|
||||||
|
"""
|
||||||
|
SYSTEM = 0
|
||||||
|
NULL = 0xFFF
|
||||||
|
|
||||||
|
|
||||||
|
class OutputPortSet(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Devices which contain an "output" port.
|
||||||
|
"""
|
||||||
|
CHIP_1 = 0
|
||||||
|
CHIP_2 = 1
|
||||||
|
CHIP_3 = 2
|
||||||
|
CHIP_4 = 3
|
||||||
|
CHIP_5 = 4
|
||||||
|
CHIP_6 = 5
|
||||||
|
CHIP_7 = 6
|
||||||
|
CHIP_8 = 7
|
||||||
|
CHIP_9 = 8
|
||||||
|
CHIP_10 = 9
|
||||||
|
CHIP_11 = 10
|
||||||
|
CHIP_12 = 11
|
||||||
|
CHIP_13 = 12
|
||||||
|
CHIP_14 = 13
|
||||||
|
CHIP_15 = 14
|
||||||
|
CHIP_16 = 15
|
||||||
|
CHIP_17 = 16
|
||||||
|
CHIP_18 = 17
|
||||||
|
CHIP_19 = 18
|
||||||
|
CHIP_20 = 19
|
||||||
|
CHIP_21 = 20
|
||||||
|
CHIP_22 = 21
|
||||||
|
CHIP_23 = 22
|
||||||
|
CHIP_24 = 23
|
||||||
|
CHIP_25 = 24
|
||||||
|
CHIP_26 = 25
|
||||||
|
CHIP_27 = 26
|
||||||
|
CHIP_28 = 27
|
||||||
|
CHIP_29 = 28
|
||||||
|
CHIP_30 = 29
|
||||||
|
CHIP_31 = 30
|
||||||
|
CHIP_32 = 31
|
||||||
|
PREVIEW = 0xFFD
|
||||||
|
METRONOME = 0xFFE
|
||||||
|
NULL = 0xFFF
|
||||||
|
|
||||||
|
|
||||||
|
class WaveFX(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Used in :attr:`chipchune.furnace.data_types.InsFeatureWaveSynth.effect`.
|
||||||
|
"""
|
||||||
|
NONE = 0
|
||||||
|
|
||||||
|
# single waveform
|
||||||
|
INVERT = 1
|
||||||
|
ADD = 2
|
||||||
|
SUBTRACT = 3
|
||||||
|
AVERAGE = 4
|
||||||
|
PHASE = 5
|
||||||
|
CHORUS = 6
|
||||||
|
|
||||||
|
# double waveform
|
||||||
|
NONE_DUAL = 128
|
||||||
|
WIPE = 129
|
||||||
|
FADE = 130
|
||||||
|
PING_PONG = 131
|
||||||
|
OVERLAY = 132
|
||||||
|
NEGATIVE_OVERLAY = 133
|
||||||
|
SLIDE = 134
|
||||||
|
MIX = 135
|
||||||
|
PHASE_MOD = 136
|
||||||
|
|
||||||
|
|
||||||
|
class ESFilterMode(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Used in :attr:`chipchune.furnace.data_types.InsFeatureES5506.filter_mode`.
|
||||||
|
"""
|
||||||
|
HPK2_HPK2 = 0
|
||||||
|
HPK2_LPK1 = 1
|
||||||
|
LPK2_LPK2 = 2
|
||||||
|
LPK2_LPK1 = 3
|
||||||
|
|
||||||
|
|
||||||
|
class GainMode(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Used in :attr:`chipchune.furnace.data_types.InsFeatureSNES.gain_mode`.
|
||||||
|
"""
|
||||||
|
DIRECT = 0
|
||||||
|
DEC_LINEAR = 4
|
||||||
|
DEC_LOG = 5
|
||||||
|
INC_LINEAR = 6
|
||||||
|
INC_INVLOG = 7
|
||||||
|
|
||||||
|
|
||||||
|
class SNESSusMode(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Used in :attr:`chipchune.furnace.data_types.InsFeatureSNES.sus`.
|
||||||
|
"""
|
||||||
|
DIRECT = 0
|
||||||
|
SUS_WITH_DEC = 1
|
||||||
|
SUS_WITH_EXP = 2
|
||||||
|
SUS_WITH_REL = 3
|
||||||
|
|
||||||
|
|
||||||
|
class _FurInsImportType(EnumShowNameOnly, EnumValueEquals):
|
||||||
|
"""
|
||||||
|
Also only used in this parser to differentiate between different types of instrument formats.
|
||||||
|
"""
|
||||||
|
# Old format
|
||||||
|
FORMAT_0_FILE = 0
|
||||||
|
FORMAT_0_EMBED = 1
|
||||||
|
|
||||||
|
# Dev127 format
|
||||||
|
FORMAT_1_FILE = 2
|
||||||
|
FORMAT_1_EMBED = 3
|
||||||
|
|
||||||
|
class _FurWavetableImportType(EnumShowNameOnly, EnumValueEquals):
|
||||||
|
"""
|
||||||
|
Also only used in this parser to differentiate between different types of wavetable formats.
|
||||||
|
"""
|
||||||
|
FILE = 0
|
||||||
|
EMBED = 1
|
||||||
|
|
||||||
|
class _FurSampleType(EnumShowNameOnly, EnumValueEquals):
|
||||||
|
"""
|
||||||
|
Also only used in this parser to differentiate between different types of sample formats.
|
||||||
|
"""
|
||||||
|
PCM_1_BIT = 0
|
||||||
|
DPCM = 1
|
||||||
|
YMZ = 3
|
||||||
|
QSOUND = 4
|
||||||
|
ADPCM_A = 5
|
||||||
|
ADPCM_B = 6
|
||||||
|
K05_ADPCM = 7
|
||||||
|
PCM_8_BIT = 8
|
||||||
|
BRR = 9
|
||||||
|
VOX = 10
|
||||||
|
ULAW = 11
|
||||||
|
C219 = 12
|
||||||
|
IMA = 13
|
||||||
|
PCM_16_BIT = 16
|
||||||
1572
loader/samples/minexample/furC64/chipchune/furnace/instrument.py
Normal file
1572
loader/samples/minexample/furC64/chipchune/furnace/instrument.py
Normal file
File diff suppressed because it is too large
Load diff
1111
loader/samples/minexample/furC64/chipchune/furnace/module.py
Normal file
1111
loader/samples/minexample/furC64/chipchune/furnace/module.py
Normal file
File diff suppressed because it is too large
Load diff
47
loader/samples/minexample/furC64/chipchune/furnace/sample.py
Normal file
47
loader/samples/minexample/furC64/chipchune/furnace/sample.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import Optional, Union, BinaryIO, List
|
||||||
|
|
||||||
|
from chipchune._util import read_short, read_int, read_str
|
||||||
|
from .data_types import SampleMeta
|
||||||
|
from .enums import _FurSampleType
|
||||||
|
|
||||||
|
FILE_MAGIC_STR = b'SMP2'
|
||||||
|
|
||||||
|
|
||||||
|
class FurnaceSample:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.meta: SampleMeta = SampleMeta()
|
||||||
|
"""
|
||||||
|
Sample metadata.
|
||||||
|
"""
|
||||||
|
self.data: bytearray = b''
|
||||||
|
"""
|
||||||
|
Sample data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def load_from_stream(self, stream: BinaryIO) -> None:
|
||||||
|
"""
|
||||||
|
Load a sample from an **uncompressed** stream.
|
||||||
|
|
||||||
|
:param stream: File-like object containing the uncompressed wavetable.
|
||||||
|
"""
|
||||||
|
if stream.read(len(FILE_MAGIC_STR)) != FILE_MAGIC_STR:
|
||||||
|
raise ValueError('Bad magic value for a wavetable file')
|
||||||
|
blk_size = read_int(stream)
|
||||||
|
if blk_size > 0:
|
||||||
|
smp_data = BytesIO(stream.read(blk_size))
|
||||||
|
else:
|
||||||
|
smp_data = stream
|
||||||
|
|
||||||
|
self.meta.name = read_str(smp_data)
|
||||||
|
self.meta.length = read_int(smp_data)
|
||||||
|
read_int(smp_data) # compatablity rate
|
||||||
|
self.meta.sample_rate = read_int(smp_data)
|
||||||
|
self.meta.depth = int(smp_data.read(1)[0])
|
||||||
|
smp_data.read(1) # loop direction
|
||||||
|
smp_data.read(1) # flags
|
||||||
|
smp_data.read(1) # flags 2
|
||||||
|
self.meta.loop_start = read_int(smp_data)
|
||||||
|
self.meta.loop_end = read_int(smp_data)
|
||||||
|
smp_data.read(16) # sample presence bitfields
|
||||||
|
self.data = smp_data.read(self.meta.length)
|
||||||
101
loader/samples/minexample/furC64/chipchune/furnace/wavetable.py
Normal file
101
loader/samples/minexample/furC64/chipchune/furnace/wavetable.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import Optional, Union, BinaryIO, List
|
||||||
|
|
||||||
|
from chipchune._util import read_short, read_int, read_str
|
||||||
|
from .data_types import WavetableMeta
|
||||||
|
from .enums import _FurWavetableImportType
|
||||||
|
|
||||||
|
FILE_MAGIC_STR = b'-Furnace waveta-'
|
||||||
|
EMBED_MAGIC_STR = b'WAVE'
|
||||||
|
|
||||||
|
|
||||||
|
class FurnaceWavetable:
|
||||||
|
def __init__(self, file_name: Optional[str] = None) -> None:
|
||||||
|
"""
|
||||||
|
Creates or opens a new Furnace wavetable as a Python object.
|
||||||
|
|
||||||
|
:param file_name: (Optional)
|
||||||
|
If specified, then it will parse a file as a FurnaceWavetable. If file name (str) is
|
||||||
|
given, it will load that file.
|
||||||
|
|
||||||
|
Defaults to None.
|
||||||
|
"""
|
||||||
|
self.file_name: Optional[str] = None
|
||||||
|
"""
|
||||||
|
Original file name, if the object was initialized with one.
|
||||||
|
"""
|
||||||
|
self.meta: WavetableMeta = WavetableMeta()
|
||||||
|
"""
|
||||||
|
Wavetable metadata.
|
||||||
|
"""
|
||||||
|
self.data: List[int] = []
|
||||||
|
"""
|
||||||
|
Wavetable data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(file_name, str):
|
||||||
|
self.load_from_file(file_name)
|
||||||
|
|
||||||
|
def load_from_file(self, file_name: Optional[str] = None) -> None:
|
||||||
|
if isinstance(file_name, str):
|
||||||
|
self.file_name = file_name
|
||||||
|
if self.file_name is None:
|
||||||
|
raise RuntimeError('No file name set, either set self.file_name or pass file_name to the function')
|
||||||
|
|
||||||
|
# since we're loading from an uncompressed file, we can just check the file magic number
|
||||||
|
with open(self.file_name, 'rb') as f:
|
||||||
|
detect_magic = f.peek(len(FILE_MAGIC_STR))[:len(FILE_MAGIC_STR)]
|
||||||
|
if detect_magic == FILE_MAGIC_STR:
|
||||||
|
return self.load_from_stream(f, _FurWavetableImportType.FILE)
|
||||||
|
else: # uncompressed for sure
|
||||||
|
raise ValueError('No recognized file type magic')
|
||||||
|
|
||||||
|
def load_from_bytes(self, data: bytes, import_as: Union[int, _FurWavetableImportType]) -> None:
|
||||||
|
"""
|
||||||
|
Load a wavetable from a series of bytes.
|
||||||
|
|
||||||
|
:param data: Bytes
|
||||||
|
"""
|
||||||
|
return self.load_from_stream(
|
||||||
|
BytesIO(data),
|
||||||
|
import_as
|
||||||
|
)
|
||||||
|
|
||||||
|
def load_from_stream(self, stream: BinaryIO, import_as: Union[int, _FurWavetableImportType]) -> None:
|
||||||
|
"""
|
||||||
|
Load a wavetable from an **uncompressed** stream.
|
||||||
|
|
||||||
|
:param stream: File-like object containing the uncompressed wavetable.
|
||||||
|
:param import_as: int
|
||||||
|
- 0 = wavetable file
|
||||||
|
- 1 = wavetable embedded in module
|
||||||
|
"""
|
||||||
|
if import_as == _FurWavetableImportType.FILE:
|
||||||
|
if stream.read(len(FILE_MAGIC_STR)) != FILE_MAGIC_STR:
|
||||||
|
raise ValueError('Bad magic value for a wavetable file')
|
||||||
|
version = read_short(stream)
|
||||||
|
read_short(stream) # reserved
|
||||||
|
self.__load_embed(stream)
|
||||||
|
|
||||||
|
elif import_as == _FurWavetableImportType.EMBED:
|
||||||
|
return self.__load_embed(stream)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid import type')
|
||||||
|
|
||||||
|
def __load_embed(self, stream: BinaryIO) -> None:
|
||||||
|
if stream.read(len(EMBED_MAGIC_STR)) != EMBED_MAGIC_STR:
|
||||||
|
raise RuntimeError('Bad magic value for a wavetable embed')
|
||||||
|
|
||||||
|
blk_size = read_int(stream)
|
||||||
|
if blk_size > 0:
|
||||||
|
wt_data = BytesIO(stream.read(blk_size))
|
||||||
|
else:
|
||||||
|
wt_data = stream
|
||||||
|
|
||||||
|
self.meta.name = read_str(wt_data)
|
||||||
|
self.meta.width = read_int(wt_data)
|
||||||
|
read_int(wt_data) # reserved
|
||||||
|
self.meta.height = read_int(wt_data) + 1 # serialized height is 1 lower than actual value
|
||||||
|
|
||||||
|
self.data = [read_int(wt_data) for _ in range(self.meta.width)]
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""
|
||||||
|
Generic format for manipulating to, from, and between tracker formats.
|
||||||
|
|
||||||
|
- :mod:`chipchune.interchange.enums`: Various constants.
|
||||||
|
- :mod:`chipchune.interchange.furnace`: Adapters for Furnace.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import enum
|
||||||
|
from chipchune._util import EnumShowNameOnly
|
||||||
|
|
||||||
|
class InterNote(EnumShowNameOnly):
|
||||||
|
"""
|
||||||
|
Common note interchange format.
|
||||||
|
"""
|
||||||
|
__ = enum.auto() # Signifies a blank space in tracker
|
||||||
|
C_ = enum.auto()
|
||||||
|
Cs = enum.auto()
|
||||||
|
D_ = enum.auto()
|
||||||
|
Ds = enum.auto()
|
||||||
|
E_ = enum.auto()
|
||||||
|
F_ = enum.auto()
|
||||||
|
Fs = enum.auto()
|
||||||
|
G_ = enum.auto()
|
||||||
|
Gs = enum.auto()
|
||||||
|
A_ = enum.auto()
|
||||||
|
As = enum.auto()
|
||||||
|
B_ = enum.auto()
|
||||||
|
Off = enum.auto()
|
||||||
|
OffRel = enum.auto()
|
||||||
|
Rel = enum.auto()
|
||||||
|
Echo = enum.auto()
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
from chipchune.furnace.enums import Note as FurnaceNote
|
||||||
|
from chipchune.interchange.enums import InterNote
|
||||||
|
|
||||||
|
def furnace_note_to_internote(note: FurnaceNote) -> InterNote:
|
||||||
|
"""
|
||||||
|
Convert a Furnace note into an InterNote.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
- Exception: If the supplied note is out of range.
|
||||||
|
"""
|
||||||
|
if note == FurnaceNote.__: return InterNote.__
|
||||||
|
elif note == FurnaceNote.C_: return InterNote.C_
|
||||||
|
elif note == FurnaceNote.Cs: return InterNote.Cs
|
||||||
|
elif note == FurnaceNote.D_: return InterNote.D_
|
||||||
|
elif note == FurnaceNote.Ds: return InterNote.Ds
|
||||||
|
elif note == FurnaceNote.E_: return InterNote.E_
|
||||||
|
elif note == FurnaceNote.F_: return InterNote.F_
|
||||||
|
elif note == FurnaceNote.Fs: return InterNote.Fs
|
||||||
|
elif note == FurnaceNote.G_: return InterNote.G_
|
||||||
|
elif note == FurnaceNote.Gs: return InterNote.Gs
|
||||||
|
elif note == FurnaceNote.A_: return InterNote.A_
|
||||||
|
elif note == FurnaceNote.As: return InterNote.As
|
||||||
|
elif note == FurnaceNote.B_: return InterNote.B_
|
||||||
|
elif note == FurnaceNote.OFF: return InterNote.Off
|
||||||
|
elif note == FurnaceNote.OFF_REL: return InterNote.OffRel
|
||||||
|
elif note == FurnaceNote.REL: return InterNote.Rel
|
||||||
|
else:
|
||||||
|
raise Exception("Invalid note value %s" % note)
|
||||||
|
|
||||||
|
def internote_to_furnace_note(note: InterNote) -> FurnaceNote:
|
||||||
|
"""
|
||||||
|
Convert an InterNote into a Furnace note. If the equivalent
|
||||||
|
value is unable to be determined, a blank note `__` is returned.
|
||||||
|
"""
|
||||||
|
if note == InterNote.__: return FurnaceNote.__
|
||||||
|
elif note == InterNote.C_: return FurnaceNote.C_
|
||||||
|
elif note == InterNote.Cs: return FurnaceNote.Cs
|
||||||
|
elif note == InterNote.D_: return FurnaceNote.D_
|
||||||
|
elif note == InterNote.Ds: return FurnaceNote.Ds
|
||||||
|
elif note == InterNote.E_: return FurnaceNote.E_
|
||||||
|
elif note == InterNote.F_: return FurnaceNote.F_
|
||||||
|
elif note == InterNote.Fs: return FurnaceNote.Fs
|
||||||
|
elif note == InterNote.G_: return FurnaceNote.G_
|
||||||
|
elif note == InterNote.Gs: return FurnaceNote.Gs
|
||||||
|
elif note == InterNote.A_: return FurnaceNote.A_
|
||||||
|
elif note == InterNote.As: return FurnaceNote.As
|
||||||
|
elif note == InterNote.B_: return FurnaceNote.B_
|
||||||
|
elif note == InterNote.Off: return FurnaceNote.OFF
|
||||||
|
elif note == InterNote.OffRel: return FurnaceNote.OFF_REL
|
||||||
|
elif note == InterNote.Rel: return FurnaceNote.REL
|
||||||
|
else:
|
||||||
|
return FurnaceNote.__
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""
|
||||||
|
Utilities for manipulating, converting, etc. tracker data.
|
||||||
|
|
||||||
|
- :mod:`chipchune.utils.conversion`: Conversion tools.
|
||||||
|
"""
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
from chipchune.furnace.module import FurnacePattern
|
||||||
|
from chipchune.interchange.enums import InterNote
|
||||||
|
from chipchune.interchange.furnace import furnace_note_to_internote
|
||||||
|
from typing import Union, List, Tuple
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SequenceEntry:
|
||||||
|
"""
|
||||||
|
A representation of a row in note-length format. Such a format is commonly
|
||||||
|
used across different sound engines and sequenced data.
|
||||||
|
|
||||||
|
A pattern can be turned into a list of SequenceEntries, which should be easier
|
||||||
|
to convert into a format of your choice.
|
||||||
|
"""
|
||||||
|
note: InterNote
|
||||||
|
length: int
|
||||||
|
volume: int
|
||||||
|
"""
|
||||||
|
Tracker-defined volume; although this should be -1 for undefined values.
|
||||||
|
"""
|
||||||
|
octave: int
|
||||||
|
"""
|
||||||
|
Tracker-defined octave; although this should be -1 for undefined values.
|
||||||
|
"""
|
||||||
|
instrument: int
|
||||||
|
"""
|
||||||
|
Tracker-defined instrument number; although this should be -1 for undefined values.
|
||||||
|
"""
|
||||||
|
effects: List[Tuple[int, int]] = field(default_factory=list)
|
||||||
|
"""
|
||||||
|
Tracker-defined effects list; if undefined, this should be empty.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def pattern_to_sequence(pattern: Union[FurnacePattern, None]) -> List[SequenceEntry]:
|
||||||
|
"""
|
||||||
|
Interface to convert a pattern from tracker rows to a "sequence", which is
|
||||||
|
really a list of SequenceEntries.
|
||||||
|
|
||||||
|
:param pattern:
|
||||||
|
A pattern object. Supported types at the moment: `FurnacePattern`.
|
||||||
|
Anything outside of the supported types will throw a `TypeError`.
|
||||||
|
"""
|
||||||
|
if isinstance(pattern, FurnacePattern):
|
||||||
|
return furnace_pattern_to_sequence(pattern)
|
||||||
|
else:
|
||||||
|
raise TypeError("Invalid pattern type; must be one of: FurnacePattern")
|
||||||
|
|
||||||
|
def furnace_pattern_to_sequence(pattern: FurnacePattern) -> List[SequenceEntry]:
|
||||||
|
converted: List[SequenceEntry] = []
|
||||||
|
last_volume = -1
|
||||||
|
for i in pattern.data:
|
||||||
|
note = furnace_note_to_internote(i.note)
|
||||||
|
effects = i.effects
|
||||||
|
volume = i.volume
|
||||||
|
instrument = i.instrument
|
||||||
|
|
||||||
|
if effects == [(65535, 65535)]:
|
||||||
|
effects = []
|
||||||
|
|
||||||
|
if volume == 65535:
|
||||||
|
volume = last_volume
|
||||||
|
else:
|
||||||
|
last_volume = volume
|
||||||
|
|
||||||
|
if instrument == 65535:
|
||||||
|
instrument = -1
|
||||||
|
|
||||||
|
if note == InterNote.__:
|
||||||
|
if len(converted) == 0:
|
||||||
|
converted.append(
|
||||||
|
SequenceEntry(
|
||||||
|
note=InterNote.__,
|
||||||
|
length=1,
|
||||||
|
volume=volume,
|
||||||
|
octave=i.octave,
|
||||||
|
instrument=instrument,
|
||||||
|
effects=effects,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
converted[-1].length += 1
|
||||||
|
else:
|
||||||
|
converted.append(
|
||||||
|
SequenceEntry(
|
||||||
|
note=note,
|
||||||
|
length=1,
|
||||||
|
volume=volume,
|
||||||
|
octave=i.octave,
|
||||||
|
instrument=instrument,
|
||||||
|
effects=effects,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return converted
|
||||||
14
loader/samples/minexample/furC64/convert.bat
Normal file
14
loader/samples/minexample/furC64/convert.bat
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
@echo off
|
||||||
|
if [%1]==[] goto usage
|
||||||
|
python3 convert_to_asm.py %1
|
||||||
|
echo converted .fur file to .asm!
|
||||||
|
cd asm
|
||||||
|
cl65 -d -vm -l furC64.lst -g -u __EXEHDR__ -t c64 -C .\c64-asm.cfg -m furC64.map -Ln furC64.lbl -o furC64-test.prg furC64.asm
|
||||||
|
@echo compiled .prg file at asm/furC64-test.prg
|
||||||
|
cd ..
|
||||||
|
goto :eof
|
||||||
|
:usage
|
||||||
|
@echo No arguments supplied
|
||||||
|
@echo Make sure to run this command with an argument
|
||||||
|
@echo example: convert.bat test_file.fur
|
||||||
|
exit /B 1
|
||||||
15
loader/samples/minexample/furC64/convert.sh
Executable file
15
loader/samples/minexample/furC64/convert.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
if [ $# -eq 0 ]
|
||||||
|
then
|
||||||
|
echo "No arguments supplied"
|
||||||
|
echo "Make sure to run this command with an argument"
|
||||||
|
echo "example: convert.sh test_file.fur"
|
||||||
|
else
|
||||||
|
python3 convert_to_asm.py $1
|
||||||
|
echo "converted .fur file to .asm!"
|
||||||
|
cd asm
|
||||||
|
cl65 -d -vm -l furC64.lst -g -u __EXEHDR__ -t c64 -C ./exe.cfg -m furC64.map -Ln furC64.lbl -o furC64-test.prg furC64.asm
|
||||||
|
cl65 -d -C ./bin.cfg -o song.bin furC64.asm
|
||||||
|
echo "compiled .prg file at asm/furC64-test.prg"
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
780
loader/samples/minexample/furC64/convert_to_asm.py
Normal file
780
loader/samples/minexample/furC64/convert_to_asm.py
Normal file
|
|
@ -0,0 +1,780 @@
|
||||||
|
from chipchune.furnace.module import FurnaceModule
|
||||||
|
from chipchune.furnace.data_types import InsFeatureMacro, InsFeatureC64, InsFeatureAmiga
|
||||||
|
from chipchune.furnace.enums import MacroCode, MacroItem, MacroType
|
||||||
|
from chipchune.furnace.enums import InstrumentType
|
||||||
|
import sys
|
||||||
|
|
||||||
|
subsong = 0
|
||||||
|
note_transpose = 0
|
||||||
|
dups = {}
|
||||||
|
|
||||||
|
print(sys.argv)
|
||||||
|
module = FurnaceModule(sys.argv[1])
|
||||||
|
chnum = module.get_num_channels()
|
||||||
|
|
||||||
|
speed_type = len(module.subsongs[subsong].speed_pattern)
|
||||||
|
|
||||||
|
notes = ["C_","Cs","D_","Ds","E_","F_","Fs","G_","Gs","A_","As","B_"]
|
||||||
|
|
||||||
|
def comp(pat):
|
||||||
|
i = 0
|
||||||
|
o = []
|
||||||
|
n = 0
|
||||||
|
while i < len(pat):
|
||||||
|
j = i
|
||||||
|
k = 0
|
||||||
|
if pat[i] >= 0x40 and pat[i] < 128: i += 1
|
||||||
|
elif pat[i] == 0xFB: i += 2
|
||||||
|
elif pat[i] == 0xFC: i += 2
|
||||||
|
elif pat[i] == 0xE0: i += 2
|
||||||
|
elif pat[i] == 0xE1: i += 2
|
||||||
|
elif pat[i] == 0xE2: i += 2
|
||||||
|
elif pat[i] == 0xE3: i += 2
|
||||||
|
elif pat[i] == 0xE4: i += 2
|
||||||
|
elif pat[i] == 0xE5: i += 3
|
||||||
|
elif pat[i] == 0xE6: i += 2
|
||||||
|
elif pat[i] == 0xE7: i += 2
|
||||||
|
elif pat[i] == 0xE8: i += 2
|
||||||
|
elif pat[i] == 0xE9: i += 3
|
||||||
|
elif pat[i] == 0xEA: i += 3
|
||||||
|
elif pat[i] == 0xEB: i += 2
|
||||||
|
elif pat[i] == 0xEC: i += 2
|
||||||
|
elif pat[i] == 0xED: i += 2
|
||||||
|
elif pat[i] == 0xEE: i += 2
|
||||||
|
elif pat[i] == 0xEF: i += 1
|
||||||
|
elif pat[i] == 0xF0: i += 1
|
||||||
|
elif pat[i] == 0xF1: i += 2
|
||||||
|
elif pat[i] == 0xFF: i += 2
|
||||||
|
elif pat[i] == 0xFD:
|
||||||
|
i += 1
|
||||||
|
n = 2
|
||||||
|
elif pat[i] == 0xFE:
|
||||||
|
i += 1
|
||||||
|
n = 2
|
||||||
|
elif pat[i] >= 128:
|
||||||
|
i += 1
|
||||||
|
n = 2
|
||||||
|
else:
|
||||||
|
k = 1
|
||||||
|
if n == 0:
|
||||||
|
o.append(pat[i])
|
||||||
|
elif pat[i] > 1:
|
||||||
|
o.append(pat[i]-1)
|
||||||
|
#print(i,pat[i])
|
||||||
|
i += 1
|
||||||
|
n = max(n-1,0)
|
||||||
|
if k == 0:
|
||||||
|
o.extend(pat[j:i])
|
||||||
|
#print(pat,"\n",o,"\n")
|
||||||
|
return o
|
||||||
|
|
||||||
|
def conv_pattern(pattern):
|
||||||
|
out = [0]
|
||||||
|
oldtemp = [0,0]
|
||||||
|
r = 0
|
||||||
|
bitind = 0
|
||||||
|
oldins = -1
|
||||||
|
for row in pattern.data:
|
||||||
|
has03xx = 0
|
||||||
|
for l in row.effects:
|
||||||
|
k = list(l)
|
||||||
|
if k[0] == 0x03 and k[1] > 0:
|
||||||
|
has03xx = 1
|
||||||
|
break
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
notnote = 0
|
||||||
|
new_byte = 0
|
||||||
|
if row.instrument != 65535 and oldins != row.instrument:
|
||||||
|
new_byte = 1
|
||||||
|
if row.instrument < 0x40:
|
||||||
|
temp.append(row.instrument+0x40)
|
||||||
|
else:
|
||||||
|
temp.append(0xFB)
|
||||||
|
temp.append(row.instrument)
|
||||||
|
oldins = row.instrument
|
||||||
|
if row.volume != 65535:
|
||||||
|
new_byte = 1
|
||||||
|
temp.append(0xFC)
|
||||||
|
temp.append(row.volume)
|
||||||
|
|
||||||
|
hasEffect = [-1,-1]
|
||||||
|
has0Dxx = -1
|
||||||
|
for l in row.effects:
|
||||||
|
k = list(l)
|
||||||
|
if k[1] == 65535:
|
||||||
|
k[1] = 0
|
||||||
|
|
||||||
|
if k[0] == 0xD:
|
||||||
|
new_byte = 1
|
||||||
|
has0Dxx = k[1]
|
||||||
|
continue
|
||||||
|
if k[0] == 0x0B:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xED, k[1]])
|
||||||
|
has0Dxx = 0
|
||||||
|
continue
|
||||||
|
if (k[0] == 0x09 or k[0] == 0x0F) and (speed_type == 1):
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE1, k[1]])
|
||||||
|
temp.extend([0xE0, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x0F and (speed_type == 2):
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE1, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x09 and (speed_type == 2):
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE0, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x00:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE2, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x01:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE3, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x02:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE4, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x03 and k[1] == 0:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE4, 0])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x03 and k[1] > 0:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE5, k[1], max(min(notes.index(str(row.note))+(row.octave*12)+note_transpose,95),0)])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x04:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE6, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x1B:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE7, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x1C:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE8, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0xE1:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xE9, k[1]>>4, k[1]&15])
|
||||||
|
continue
|
||||||
|
if k[0] == 0xE2:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xEA, k[1]>>4, k[1]&15])
|
||||||
|
continue
|
||||||
|
if k[0] == 0xE5:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xEB, k[1]])
|
||||||
|
continue
|
||||||
|
if k[0] == 0xEC:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xEC, k[1]])
|
||||||
|
continue
|
||||||
|
if (k[0]>>4) == 4:
|
||||||
|
new_byte = 1
|
||||||
|
temp.extend([0xEE, (k[1]|(k[0]&0xf)<<8)>>3])
|
||||||
|
continue
|
||||||
|
if k[0] == 0xEA:
|
||||||
|
new_byte = 1
|
||||||
|
if k[1] == 0:
|
||||||
|
temp.extend([0xEF])
|
||||||
|
else:
|
||||||
|
temp.extend([0xF0])
|
||||||
|
continue
|
||||||
|
if k[0] == 0x1A:
|
||||||
|
new_byte = 1
|
||||||
|
if k[1] > 0:
|
||||||
|
temp.extend([0xF1, 0x00])
|
||||||
|
else:
|
||||||
|
temp.extend([0xF1, 0xFF])
|
||||||
|
continue
|
||||||
|
if str(row.note) == "OFF_REL":
|
||||||
|
notnote = 1
|
||||||
|
new_byte = 1
|
||||||
|
temp.append(0xFD)
|
||||||
|
elif str(row.note) == "REL":
|
||||||
|
notnote = 1
|
||||||
|
new_byte = 1
|
||||||
|
temp.append(0xFD)
|
||||||
|
elif str(row.note) == "OFF":
|
||||||
|
notnote = 1
|
||||||
|
new_byte = 1
|
||||||
|
temp.append(0xFE)
|
||||||
|
elif str(row.note) == "__" or (has03xx == 1):
|
||||||
|
if has03xx == 0:
|
||||||
|
notnote = 1
|
||||||
|
#temp.append(0x80)
|
||||||
|
else:
|
||||||
|
new_byte = 1
|
||||||
|
temp.append(max(min(notes.index(str(row.note))+(row.octave*12)+note_transpose,95),0)+0x80)
|
||||||
|
|
||||||
|
if new_byte == 1:
|
||||||
|
temp.append(0)
|
||||||
|
out.extend(temp)
|
||||||
|
durpass = False
|
||||||
|
if out[-1] >= 63:
|
||||||
|
out.append(0)
|
||||||
|
if has0Dxx > -1:
|
||||||
|
out[-1] += 1
|
||||||
|
if out[0] == 0: out = out[1:]
|
||||||
|
out.extend([0xFF, has0Dxx])
|
||||||
|
return out
|
||||||
|
out[-1] += 1
|
||||||
|
r += 1
|
||||||
|
out.extend([0xFF, 0])
|
||||||
|
if out[0] == 0: out = out[1:]
|
||||||
|
return out
|
||||||
|
|
||||||
|
f = open("asm/song.asm","w")
|
||||||
|
|
||||||
|
relW = []
|
||||||
|
relA = []
|
||||||
|
relD = []
|
||||||
|
relC = []
|
||||||
|
|
||||||
|
f.write("ticks_init:")
|
||||||
|
f.write(".byte ")
|
||||||
|
if speed_type == 1:
|
||||||
|
f.write(str(module.subsongs[subsong].speed_pattern[0])+", ")
|
||||||
|
f.write(str(module.subsongs[subsong].speed_pattern[0])+"\n")
|
||||||
|
elif speed_type == 2:
|
||||||
|
f.write(str(module.subsongs[subsong].speed_pattern[0])+", ")
|
||||||
|
f.write(str(module.subsongs[subsong].speed_pattern[1])+"\n")
|
||||||
|
|
||||||
|
f.write("insFL:\n")
|
||||||
|
f.write(".lobytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"F")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
f.write("insFH:\n")
|
||||||
|
f.write(".hibytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"F")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
|
||||||
|
f.write("insAL:\n")
|
||||||
|
f.write(".lobytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"A")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
f.write("insAH:\n")
|
||||||
|
f.write(".hibytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"A")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
|
||||||
|
f.write("insDL:\n")
|
||||||
|
f.write(".lobytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"D")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
f.write("insDH:\n")
|
||||||
|
f.write(".hibytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"D")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
|
||||||
|
f.write("insWL:\n")
|
||||||
|
f.write(".lobytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"W")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
f.write("insWH:\n")
|
||||||
|
f.write(".hibytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"W")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
|
||||||
|
f.write("insCL:\n")
|
||||||
|
f.write(".lobytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"C")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
f.write("insCH:\n")
|
||||||
|
f.write(".hibytes ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
f.write("ins"+str(i)+"C")
|
||||||
|
if i == len(module.instruments)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
features = module.instruments[i].features
|
||||||
|
a = filter(
|
||||||
|
lambda x: (
|
||||||
|
type(x) == InsFeatureMacro
|
||||||
|
), features
|
||||||
|
)
|
||||||
|
macros = []
|
||||||
|
for j in a:
|
||||||
|
macros = j.macros
|
||||||
|
hasWaveMacro = 0
|
||||||
|
hasCutMacro = 0
|
||||||
|
for j in macros:
|
||||||
|
kind = j.kind
|
||||||
|
if kind == MacroCode.WAVE:
|
||||||
|
hasWaveMacro = 1
|
||||||
|
continue
|
||||||
|
if kind == MacroCode.ALG:
|
||||||
|
hasCutMacro = 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
hasAbsFilter = False
|
||||||
|
hasAbsDuty = False
|
||||||
|
written_ins = False
|
||||||
|
|
||||||
|
a = filter(
|
||||||
|
lambda x: (
|
||||||
|
type(x) == InsFeatureMacro
|
||||||
|
), features
|
||||||
|
)
|
||||||
|
macros = []
|
||||||
|
for j in a:
|
||||||
|
macros = j.macros
|
||||||
|
for j in macros:
|
||||||
|
kind = j.kind
|
||||||
|
if kind == MacroCode.DUTY and j.type == MacroType.LFO:
|
||||||
|
hasAbsDuty = True
|
||||||
|
|
||||||
|
a = filter(
|
||||||
|
lambda x: (
|
||||||
|
type(x) == InsFeatureC64
|
||||||
|
), features
|
||||||
|
)
|
||||||
|
for j in a:
|
||||||
|
wave = j.tri_on
|
||||||
|
wave |= j.saw_on<<1
|
||||||
|
wave |= j.pulse_on<<2
|
||||||
|
wave |= j.noise_on<<3
|
||||||
|
wave <<= 4
|
||||||
|
wave |= j.ring_mod<<2
|
||||||
|
wave |= j.osc_sync<<1
|
||||||
|
f.write("ins"+str(i)+"F:\n")
|
||||||
|
f.write(".byte ")
|
||||||
|
f.write(str(wave)+", ")
|
||||||
|
ad = (j.envelope.a<<4)|j.envelope.d
|
||||||
|
sr = (j.envelope.s<<4)|j.envelope.r
|
||||||
|
f.write(str(ad)+", ")
|
||||||
|
f.write(str(sr)+", ")
|
||||||
|
f.write(str(j.duty&0xff)+", ")
|
||||||
|
f.write(str(j.duty>>8)+", ")
|
||||||
|
flags = 0
|
||||||
|
if j.duty_is_abs:
|
||||||
|
flags |= 1
|
||||||
|
if hasWaveMacro:
|
||||||
|
flags |= 2
|
||||||
|
if j.to_filter:
|
||||||
|
flags |= 4
|
||||||
|
if j.init_filter:
|
||||||
|
flags |= 8
|
||||||
|
if j.filter_is_abs:
|
||||||
|
flags |= 16
|
||||||
|
if hasCutMacro:
|
||||||
|
flags |= 32
|
||||||
|
if not j.no_test:
|
||||||
|
flags |= 64
|
||||||
|
f.write(str(flags)+", ")
|
||||||
|
fil = 0
|
||||||
|
fil = j.lp<<4
|
||||||
|
fil |= j.bp<<5
|
||||||
|
fil |= j.hp<<6
|
||||||
|
fil |= j.ch3_off<<7
|
||||||
|
f.write(str(j.res<<4)+", ")
|
||||||
|
f.write(str(fil)+", ")
|
||||||
|
f.write(str(j.cut&0xff)+", ")
|
||||||
|
f.write(str((j.cut>>8)&15)+"\n")
|
||||||
|
f.write("\n")
|
||||||
|
written_ins = True
|
||||||
|
hasAbsDuty |= j.duty_is_abs
|
||||||
|
hasAbsFilter = j.filter_is_abs
|
||||||
|
if written_ins == False:
|
||||||
|
f.write("ins"+str(i)+"F:\n")
|
||||||
|
f.write(".byte 32, 8, 0, 255, 7, 0\n")
|
||||||
|
a = filter(
|
||||||
|
lambda x: (
|
||||||
|
type(x) == InsFeatureMacro
|
||||||
|
), features
|
||||||
|
)
|
||||||
|
arp = [128,0xFF,0xFF]
|
||||||
|
duty = [0xFF,0xFF]
|
||||||
|
wave = [0xFF,0xFF]
|
||||||
|
cutoff = [0xFF,0xFF]
|
||||||
|
macros = []
|
||||||
|
for j in a:
|
||||||
|
macros = j.macros
|
||||||
|
hasRelTotal = [0,0,0,0]
|
||||||
|
for j in macros:
|
||||||
|
kind = j.kind
|
||||||
|
if kind == MacroCode.ARP:
|
||||||
|
s = j.speed
|
||||||
|
arp = []
|
||||||
|
loop = 0xff
|
||||||
|
hasRel = 0
|
||||||
|
oldlen = 0
|
||||||
|
if j.data[-1] == MacroItem.LOOP:
|
||||||
|
arr = [MacroItem.LOOP, j.data[-2]]
|
||||||
|
j.data = j.data[:-2] + arr
|
||||||
|
for k in j.data:
|
||||||
|
if k == MacroItem.LOOP:
|
||||||
|
loop = oldlen
|
||||||
|
elif k == MacroItem.RELEASE:
|
||||||
|
arp.append(0xFF)
|
||||||
|
arp.append(loop)
|
||||||
|
relA.append(len(arp))
|
||||||
|
oldlen = max(len(arp),0)
|
||||||
|
hasRel = 1
|
||||||
|
elif (k>>30) > 0:
|
||||||
|
arp.append(0xFE)
|
||||||
|
k = abs(k^(1<<30))
|
||||||
|
k = max(min(k,95),0)
|
||||||
|
arp.append(k%120)
|
||||||
|
oldlen = max(len(arp),0)
|
||||||
|
else:
|
||||||
|
if k < 0:
|
||||||
|
arp.append((k%120)-120+128)
|
||||||
|
else:
|
||||||
|
arp.append((k%120)+128)
|
||||||
|
oldlen = max(len(arp),0)
|
||||||
|
if hasRel == 0:
|
||||||
|
relA.append(len(arp))
|
||||||
|
hasRelTotal[1] = 1
|
||||||
|
len_temp = len(arp)
|
||||||
|
arp.append(0xFF)
|
||||||
|
arp.append(loop if loop!=len_temp else 0xff)
|
||||||
|
if kind == MacroCode.DUTY and j.type == MacroType.LFO:
|
||||||
|
while type(j.data[0]) is not int:
|
||||||
|
j.data = j.data[1:]
|
||||||
|
|
||||||
|
s = j.speed
|
||||||
|
duty = []
|
||||||
|
hasRel = 0
|
||||||
|
lfo = j.data[13]
|
||||||
|
while lfo <= 1023:
|
||||||
|
lfo += j.data[11]
|
||||||
|
if lfo > 1023: break
|
||||||
|
k = lfo
|
||||||
|
if k & 512:
|
||||||
|
k = 1023-lfo
|
||||||
|
k >>= 1
|
||||||
|
k = j.data[0]+((k+(j.data[1]-j.data[0])*k)>>8)
|
||||||
|
if (k&0xff) == 255:
|
||||||
|
duty.append(254)
|
||||||
|
else:
|
||||||
|
duty.append(k&0xff)
|
||||||
|
duty.append(k>>8)
|
||||||
|
if hasRel == 0:
|
||||||
|
relD.append(0)
|
||||||
|
hasRelTotal[2] = 1
|
||||||
|
duty.append(0xFF)
|
||||||
|
duty.append(0)
|
||||||
|
elif kind == MacroCode.DUTY:
|
||||||
|
s = j.speed
|
||||||
|
duty = []
|
||||||
|
loop = 0xff
|
||||||
|
loop2 = 0
|
||||||
|
hasRel = 0
|
||||||
|
for k in j.data:
|
||||||
|
if k == MacroItem.LOOP:
|
||||||
|
loop = loop2
|
||||||
|
elif k == MacroItem.RELEASE:
|
||||||
|
duty.append(0xFF)
|
||||||
|
duty.append(loop)
|
||||||
|
relD.append(len(duty))
|
||||||
|
hasRel = 1
|
||||||
|
else:
|
||||||
|
loop2 = len(duty)+2
|
||||||
|
if hasAbsDuty:
|
||||||
|
if (k&0xff) == 255:
|
||||||
|
duty.append(254)
|
||||||
|
else:
|
||||||
|
duty.append(k&0xff)
|
||||||
|
duty.append(k>>8)
|
||||||
|
else:
|
||||||
|
k = 32768-(k*4) #4)
|
||||||
|
duty.append(k&0xff)
|
||||||
|
duty.append(k>>8)
|
||||||
|
if hasRel == 0:
|
||||||
|
relD.append(len(duty))
|
||||||
|
hasRelTotal[2] = 1
|
||||||
|
len_temp = len(duty)
|
||||||
|
duty.append(0xFF)
|
||||||
|
duty.append(loop if loop!=len_temp else 0xff)
|
||||||
|
if kind == MacroCode.ALG and j.type == MacroType.LFO:
|
||||||
|
while type(j.data[0]) is not int:
|
||||||
|
j.data = j.data[1:]
|
||||||
|
|
||||||
|
s = j.speed
|
||||||
|
cutoff = []
|
||||||
|
hasRel = 0
|
||||||
|
lfo = j.data[13]
|
||||||
|
while lfo <= 1023:
|
||||||
|
lfo += j.data[11]
|
||||||
|
k = lfo
|
||||||
|
if k & 512:
|
||||||
|
k = 1023-lfo
|
||||||
|
k >>= 1
|
||||||
|
k = j.data[0]+((k+(j.data[1]-j.data[0])*k)>>8)
|
||||||
|
if (k&0xff) == 255:
|
||||||
|
cutoff.append(254)
|
||||||
|
else:
|
||||||
|
cutoff.append(k&0xff)
|
||||||
|
cutoff.append(k>>8)
|
||||||
|
if hasRel == 0:
|
||||||
|
relC.append(len(cutoff))
|
||||||
|
hasRelTotal[3] = 1
|
||||||
|
cutoff.append(0xFF)
|
||||||
|
cutoff.append(0)
|
||||||
|
elif kind == MacroCode.ALG:
|
||||||
|
s = j.speed
|
||||||
|
cutoff = []
|
||||||
|
loop = 0xff
|
||||||
|
loop2 = 0
|
||||||
|
hasRel = 0
|
||||||
|
for k in j.data:
|
||||||
|
if k == MacroItem.LOOP:
|
||||||
|
loop = loop2
|
||||||
|
elif k == MacroItem.RELEASE:
|
||||||
|
cutoff.append(0xFF)
|
||||||
|
cutoff.append(loop)
|
||||||
|
relC.append(len(cutoff))
|
||||||
|
hasRel = 1
|
||||||
|
else:
|
||||||
|
loop2 = len(cutoff)+1
|
||||||
|
if hasAbsFilter:
|
||||||
|
if (k&0xff) == 255:
|
||||||
|
cutoff.append(254)
|
||||||
|
else:
|
||||||
|
cutoff.append(k&0xff)
|
||||||
|
cutoff.append(k>>8)
|
||||||
|
else:
|
||||||
|
k = 32768+k*7
|
||||||
|
cutoff.append(k&0xff)
|
||||||
|
cutoff.append(k>>8)
|
||||||
|
if hasRel == 0:
|
||||||
|
relC.append(len(cutoff))
|
||||||
|
hasRelTotal[3] = 1
|
||||||
|
len_temp = len(cutoff)
|
||||||
|
cutoff.append(0xFF)
|
||||||
|
cutoff.append(loop if loop!=len_temp else 0xff)
|
||||||
|
if kind == MacroCode.WAVE:
|
||||||
|
s = j.speed
|
||||||
|
wave = []
|
||||||
|
loop = 0xff
|
||||||
|
loop2 = 0
|
||||||
|
hasRel = 0
|
||||||
|
for k in j.data:
|
||||||
|
if k == MacroItem.LOOP:
|
||||||
|
loop = len(wave)
|
||||||
|
elif k == MacroItem.RELEASE:
|
||||||
|
wave.append(0xFF)
|
||||||
|
wave.append(loop)
|
||||||
|
relW.append(len(wave))
|
||||||
|
hasRel = 1
|
||||||
|
else:
|
||||||
|
wave.append(k<<4)
|
||||||
|
if hasRel == 0:
|
||||||
|
relW.append(len(wave))
|
||||||
|
hasRelTotal[0] = 1
|
||||||
|
len_temp = len(wave)
|
||||||
|
wave.append(0xFF)
|
||||||
|
wave.append(loop if loop!=len_temp else 0xff)
|
||||||
|
if hasRelTotal[0] == 0:
|
||||||
|
relW.append(0)
|
||||||
|
if hasRelTotal[1] == 0:
|
||||||
|
relA.append(0)
|
||||||
|
if hasRelTotal[2] == 0:
|
||||||
|
relD.append(0)
|
||||||
|
if hasRelTotal[3] == 0:
|
||||||
|
relC.append(0)
|
||||||
|
wave = str(wave)[1:-1]
|
||||||
|
duty = str(duty)[1:-1]
|
||||||
|
arp = str(arp)[1:-1]
|
||||||
|
cutoff = str(cutoff)[1:-1]
|
||||||
|
if arp in dups:
|
||||||
|
f.write("ins"+str(i)+"A = "+dups[arp]+"\n")
|
||||||
|
else:
|
||||||
|
f.write("ins"+str(i)+"A:\n")
|
||||||
|
f.write(".byte "+arp+"\n")
|
||||||
|
dups[arp] = "ins"+str(i)+"A"
|
||||||
|
|
||||||
|
if duty in dups:
|
||||||
|
f.write("ins"+str(i)+"D = "+dups[duty]+"\n")
|
||||||
|
else:
|
||||||
|
f.write("ins"+str(i)+"D:\n")
|
||||||
|
f.write(".byte "+duty+"\n")
|
||||||
|
dups[duty] = "ins"+str(i)+"D"
|
||||||
|
|
||||||
|
if wave in dups:
|
||||||
|
f.write("ins"+str(i)+"W = "+dups[wave]+"\n")
|
||||||
|
else:
|
||||||
|
f.write("ins"+str(i)+"W:\n")
|
||||||
|
f.write(".byte "+wave+"\n")
|
||||||
|
dups[wave] = "ins"+str(i)+"W"
|
||||||
|
|
||||||
|
if cutoff in dups:
|
||||||
|
f.write("ins"+str(i)+"C = "+dups[cutoff]+"\n")
|
||||||
|
else:
|
||||||
|
f.write("ins"+str(i)+"C:\n")
|
||||||
|
f.write(".byte "+cutoff+"\n")
|
||||||
|
dups[cutoff] = "ins"+str(i)+"C"
|
||||||
|
|
||||||
|
relW = str(relW)[1:-1]
|
||||||
|
relD = str(relD)[1:-1]
|
||||||
|
relA = str(relA)[1:-1]
|
||||||
|
f.write("insArel:\n")
|
||||||
|
f.write(".byte "+relA+"\n")
|
||||||
|
f.write("insDrel:\n")
|
||||||
|
f.write(".byte "+relD+"\n")
|
||||||
|
f.write("insWrel:\n")
|
||||||
|
f.write(".byte "+relW+"\n")
|
||||||
|
|
||||||
|
for i in range(chnum):
|
||||||
|
order = module.subsongs[subsong].order[i]
|
||||||
|
f.write("order"+str(i)+"len = "+str(len(order))+"\n")
|
||||||
|
f.write("order"+str(i)+"L:\n")
|
||||||
|
f.write(".byte ")
|
||||||
|
for o in range(len(order)):
|
||||||
|
f.write("<(patCH"+str(i)+"N"+str(order[o])+"-1)")
|
||||||
|
if o == len(order)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
f.write("order"+str(i)+"H:\n")
|
||||||
|
f.write(".byte ")
|
||||||
|
for o in range(len(order)):
|
||||||
|
f.write(">(patCH"+str(i)+"N"+str(order[o])+"-1)")
|
||||||
|
if o == len(order)-1:
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
|
||||||
|
for i in range(chnum):
|
||||||
|
order = module.subsongs[subsong].order[i]
|
||||||
|
avail_patterns = filter(
|
||||||
|
lambda x: (
|
||||||
|
x.channel == i and
|
||||||
|
x.subsong == subsong
|
||||||
|
),
|
||||||
|
module.patterns
|
||||||
|
)
|
||||||
|
for p in avail_patterns:
|
||||||
|
patnum = p.index
|
||||||
|
#print(patnum,i)
|
||||||
|
g = str(comp(conv_pattern(p)))[1:-1]
|
||||||
|
f.write("patCH"+str(i)+"N"+str(patnum)+":\n")
|
||||||
|
f.write(".byte "+g+"\n")
|
||||||
|
|
||||||
|
if chnum == 4:
|
||||||
|
f.write("sampleHS:\n.hibytes ")
|
||||||
|
for i in range(len(module.samples)):
|
||||||
|
f.write("PCM"+str(i))
|
||||||
|
if i == (len(module.samples)-1):
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
|
||||||
|
f.write("sampleHE:\n.hibytes ")
|
||||||
|
for i in range(len(module.samples)):
|
||||||
|
f.write("PCMe"+str(i))
|
||||||
|
if i == (len(module.samples)-1):
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
|
||||||
|
total_maps = []
|
||||||
|
f.write("insSI:\n.byte ")
|
||||||
|
for i in range(len(module.instruments)):
|
||||||
|
features = module.instruments[i].features
|
||||||
|
a = filter(
|
||||||
|
lambda x: (
|
||||||
|
type(x) == InsFeatureAmiga
|
||||||
|
), features
|
||||||
|
)
|
||||||
|
init_sample = 0
|
||||||
|
for j in a:
|
||||||
|
if j.init_sample > 0 and j.init_sample != 65535:
|
||||||
|
init_sample = j.init_sample
|
||||||
|
break
|
||||||
|
f.write(str(init_sample))
|
||||||
|
if i == (len(module.instruments)-1):
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
f.write(", ")
|
||||||
|
|
||||||
|
f.write(".res 256-(*&$ff), 0\n")
|
||||||
|
for i in range(len(module.samples)):
|
||||||
|
sample = []
|
||||||
|
rate = max(min(module.samples[i].meta.sample_rate,384000),100)
|
||||||
|
smp = list(module.samples[i].data)
|
||||||
|
k = 0
|
||||||
|
while k < len(smp):
|
||||||
|
j = smp[int(k)]
|
||||||
|
s = float(((int(j)+128)&0xff))
|
||||||
|
s = ((s-128)/1.65)+128
|
||||||
|
#s = ((s-128)/1.6)+128
|
||||||
|
s = int(s/16)
|
||||||
|
#s = (s>>1)+8
|
||||||
|
sample.append(s)
|
||||||
|
k += rate/7812
|
||||||
|
s = sample[-1]
|
||||||
|
if (len(sample)%256) == 0:
|
||||||
|
sample.extend([s]*256)
|
||||||
|
else:
|
||||||
|
while (len(sample)%256) != 0:
|
||||||
|
sample.append(s)
|
||||||
|
f.write("PCM"+str(i)+":\n.byte "+str(sample)[1:-1]+"\n")
|
||||||
|
f.write("PCMe"+str(i)+":\n")
|
||||||
|
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# frequency calculation code taken from
|
||||||
|
# https://codebase64.org/doku.php?id=base:how_to_calculate_your_own_sid_frequency_table
|
||||||
|
|
||||||
|
tuning = module.meta.tuning
|
||||||
|
f = open("asm/note_lo.bin","wb")
|
||||||
|
for i in range(96):
|
||||||
|
hz = tuning * (2**(float(i-57)/12.0))
|
||||||
|
cnst = (256**3)/985248.0 # PAL frequency
|
||||||
|
freq = min(max(hz*cnst,0),0xffff)
|
||||||
|
f.write(bytearray([int(freq)&0xff]))
|
||||||
|
f.close()
|
||||||
|
f = open("asm/note_hi.bin","wb")
|
||||||
|
for i in range(96):
|
||||||
|
hz = tuning * (2**(float(i-57)/12.0))
|
||||||
|
cnst = (256**3)/985248.0 # PAL frequency
|
||||||
|
freq = min(max(hz*cnst,0),0xffff)
|
||||||
|
f.write(bytearray([(int(freq)>>8)&0xff]))
|
||||||
|
f.close()
|
||||||
BIN
loader/samples/minexample/guy_amb.fur
Normal file
BIN
loader/samples/minexample/guy_amb.fur
Normal file
Binary file not shown.
BIN
loader/samples/minexample/guy_appear.fur
Normal file
BIN
loader/samples/minexample/guy_appear.fur
Normal file
Binary file not shown.
|
|
@ -165,9 +165,10 @@ code_start:
|
||||||
lda #$10
|
lda #$10
|
||||||
jsr load_8000_zx02
|
jsr load_8000_zx02
|
||||||
|
|
||||||
ldx #<sidname
|
ldx #<song0name
|
||||||
ldy #>sidname
|
ldy #>song0name
|
||||||
jsr loadraw
|
lda #$a0
|
||||||
|
jsr load_8000_zx02
|
||||||
|
|
||||||
ldx #<towername
|
ldx #<towername
|
||||||
ldy #>towername
|
ldy #>towername
|
||||||
|
|
@ -1246,7 +1247,7 @@ irq_badguy:
|
||||||
|
|
||||||
badguy: .byte "badguy",0
|
badguy: .byte "badguy",0
|
||||||
fontname: .byte "font",0
|
fontname: .byte "font",0
|
||||||
sidname: .byte "sid", 0
|
song0name: .byte "song0", 0
|
||||||
introname: .byte "intrbmp", 0
|
introname: .byte "intrbmp", 0
|
||||||
towername: .byte "tower", 0
|
towername: .byte "tower", 0
|
||||||
towerbeamname: .byte "towerbm", 0
|
towerbeamname: .byte "towerbm", 0
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
BIN
loader/samples/minexample/ys2_port_legato.fur
Normal file
BIN
loader/samples/minexample/ys2_port_legato.fur
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue