Merge branch 'master' into letter-icons
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.sfd text
|
2
.gitignore
vendored
|
@ -6,6 +6,7 @@ release/
|
|||
t/
|
||||
winbuild/
|
||||
win32build/
|
||||
xpbuild/
|
||||
macbuild/
|
||||
linuxbuild/
|
||||
*.swp
|
||||
|
@ -27,3 +28,4 @@ extern/imgui_patched/examples/
|
|||
src/asm/68k/amigatest/*.bin
|
||||
src/asm/68k/amigatest/player
|
||||
res/binary_to_compressed_c
|
||||
res/binary_to_compressed_c.exe
|
||||
|
|
|
@ -507,7 +507,7 @@ src/engine/platform/sound/d65modified.c
|
|||
|
||||
src/engine/platform/sound/ted-sound.c
|
||||
|
||||
src/engine/platform/sound/c140.c
|
||||
src/engine/platform/sound/c140_c219.c
|
||||
|
||||
src/engine/platform/oplAInterface.cpp
|
||||
src/engine/platform/ym2608Interface.cpp
|
||||
|
@ -518,6 +518,7 @@ src/engine/brrUtils.c
|
|||
src/engine/safeReader.cpp
|
||||
src/engine/safeWriter.cpp
|
||||
src/engine/cmdStream.cpp
|
||||
src/engine/cmdStreamOps.cpp
|
||||
src/engine/config.cpp
|
||||
src/engine/configEngine.cpp
|
||||
src/engine/dispatchContainer.cpp
|
||||
|
@ -525,6 +526,7 @@ src/engine/engine.cpp
|
|||
src/engine/export.cpp
|
||||
src/engine/fileOps.cpp
|
||||
src/engine/fileOpsIns.cpp
|
||||
src/engine/fileOpsSample.cpp
|
||||
src/engine/filter.cpp
|
||||
src/engine/instrument.cpp
|
||||
src/engine/macroInt.cpp
|
||||
|
@ -535,6 +537,7 @@ src/engine/song.cpp
|
|||
src/engine/sysDef.cpp
|
||||
src/engine/wavetable.cpp
|
||||
src/engine/waveSynth.cpp
|
||||
src/engine/wavOps.cpp
|
||||
src/engine/vgmOps.cpp
|
||||
src/engine/zsmOps.cpp
|
||||
src/engine/zsm.cpp
|
||||
|
|
|
@ -48,8 +48,10 @@ for other operating systems, you may [build the source](#developer-info).
|
|||
- Ricoh RF5C68 used in Sega CD and FM Towns
|
||||
- OKI MSM6258 and MSM6295
|
||||
- Konami K007232
|
||||
- Konami K053260
|
||||
- Irem GA20
|
||||
- Ensoniq ES5506
|
||||
- Namco C140
|
||||
- wavetable chips:
|
||||
- HuC6280 used in PC Engine
|
||||
- Konami Bubble System WSG
|
||||
|
@ -73,6 +75,7 @@ for other operating systems, you may [build the source](#developer-info).
|
|||
- QuadTone engine
|
||||
- Pokémon Mini
|
||||
- Commodore PET
|
||||
- TED used in Commodore Plus/4
|
||||
- Casio PV-1000
|
||||
- TIA used in Atari 2600
|
||||
- POKEY used in Atari 8-bit computers
|
||||
|
@ -124,7 +127,7 @@ for other operating systems, you may [build the source](#developer-info).
|
|||
# quick references
|
||||
|
||||
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, the [official Revolt](https://rvlt.gg/GRPS6tmc) or the [official Discord server](https://discord.gg/EfrwT2wq7z).
|
||||
- **help**: check out the [documentation](doc/README.md). it's about 80% complete.
|
||||
- **help**: check out the [documentation](doc/README.md). it's about 90% complete.
|
||||
|
||||
## packages
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ android {
|
|||
}
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 26
|
||||
versionCode 162
|
||||
versionName "0.6pre7"
|
||||
versionCode 169
|
||||
versionName "0.6pre9"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static", "-DWARNINGS_ARE_ERRORS=ON"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.tildearrow.furnace"
|
||||
android:versionCode="162"
|
||||
android:versionName="0.6pre7"
|
||||
android:versionCode="169"
|
||||
android:versionName="0.6pre9"
|
||||
android:installLocation="auto">
|
||||
|
||||
<!-- OpenGL ES 2.0 -->
|
||||
|
|
BIN
demos/opl/DASH.fur
Normal file
BIN
demos/pc98/atomic_failure.fur
Normal file
|
@ -15,22 +15,22 @@ The **[pattern view](../3-pattern/README.md)** is like a spreadsheet that displa
|
|||
## structure
|
||||
|
||||
The **order list** is a smaller spreadsheet showing the overall song structure.
|
||||
- A song is made up of a list of **orders**.
|
||||
- An **order** is a set of numbered **patterns** used for each channel.
|
||||
- A song is made up of a list of orders.
|
||||
- An **order** is a set of numbered patterns used for each channel.
|
||||
- Each channel has its own unique list of patterns.
|
||||
- Each pattern contains note and effect data for that channel only.
|
||||
- Each **pattern** contains note and effect data for that channel only.
|
||||
- Patterns may be used multiple times in the order list. Changing a pattern's data in one order will affect the same pattern used in other orders.
|
||||
|
||||
## time
|
||||
|
||||
- Each pattern is made of the same number of **rows** as seen in the tracker view.
|
||||
- During playback, Each row lasts a number of **ticks** determined by its **speed** value.
|
||||
- A tick is the smallest measure of time to which all note, effect, and macro times are quantized.
|
||||
- Each pattern is made of the same number of rows as seen in the tracker view.
|
||||
- During playback, each **row** lasts a number of ticks determined by its **speed** value.
|
||||
- A **tick** is the smallest measure of time to which all note, effect, and macro times are quantized.
|
||||
|
||||
## sound
|
||||
|
||||
Different chips have different capabilities. Even within the same chip, each channel may have its own ways of making sound.
|
||||
- Some channels use one or more waveform **generators** (sine, square, noise...) to build up a sound.
|
||||
- Of special note are **[FM (frequency modulation)](../4-instrument/fm.md)** channels, which use a number of generators called **operators** that can interact to make very complex sounds.
|
||||
- Some channels use **[samples](../6-sample/README.md)** - recordings of sounds, often with defined loop points to allow a note to sustain.
|
||||
- Some channels use **[samples](../6-sample/README.md)** which are recordings of sounds, often with defined loop points to allow a note to sustain.
|
||||
- Some channels use **[wavetables](../5-wave/README.md)**, which are like very short samples of fixed length that automatically loop.
|
|
@ -6,9 +6,14 @@ the default layout of Furnace is depicted below.
|
|||
|
||||
![interface](interface1.png)
|
||||
|
||||
primary topics:
|
||||
## general info
|
||||
|
||||
- [UI components](components.md): recommended reading for all!
|
||||
- [global keyboard shortcuts](keyboard.md)
|
||||
- [menu bar](menu-bar.md)
|
||||
|
||||
## primary windows
|
||||
|
||||
- [order list](order-list.md)
|
||||
- [play/edit controls](play-edit-controls.md)
|
||||
- [instrument/wavetable/sample list](asset-list.md)
|
||||
|
@ -19,7 +24,7 @@ primary topics:
|
|||
- [wavetable editor](../5-wave/README.md)
|
||||
- [sample editor](../6-sample/README.md)
|
||||
|
||||
advanced topics:
|
||||
## advanced topics
|
||||
|
||||
- [mixer](../8-advanced/mixer.md)
|
||||
- [grooves](../8-advanced/grooves.md)
|
||||
|
@ -36,9 +41,7 @@ advanced topics:
|
|||
- [log viewer](../8-advanced/log-viewer.md)
|
||||
- [statistics](../8-advanced/stats.md)
|
||||
|
||||
other topics:
|
||||
## other topics
|
||||
|
||||
- [settings](../2-interface/settings.md)
|
||||
- [UI components](components.md)
|
||||
- [global keyboard shortcuts](keyboard.md)
|
||||
- [basic mode](basic-mode.md)
|
||||
- [settings](../2-interface/settings.md)
|
||||
|
|
Before Width: | Height: | Size: 423 B |
Before Width: | Height: | Size: 576 B |
Before Width: | Height: | Size: 648 B |
Before Width: | Height: | Size: 436 B |
|
@ -3,10 +3,20 @@
|
|||
![instruments window](instruments.png)
|
||||
|
||||
Buttons from left to right:
|
||||
- **Add**: Creates a new, default instrument.
|
||||
- **Add**: pops up a menu to select which type of instrument to add. if only one instrument type is available, the menu is skipped.
|
||||
- If the "Display instrument type menu when adding instrument" setting is disabled, this skips the menu and creates an instrument according to the chip under the cursor.
|
||||
- Right-clicking always brings up the menu.
|
||||
- **Duplicate**: Duplicates the currently selected instrument.
|
||||
- **Open**: Brings up a file dialog to load a file as a new instrument at the end of the list.
|
||||
- **Save**: Brings up a file dialog to save the currently selected instrument.
|
||||
- **Save**: Brings up a file dialog to save the currently selected asset.
|
||||
- Instruments are saved as Furnace instrument (.fui) files.
|
||||
- Wavetables are saved as Furnace wavetable (.fuw) files.
|
||||
- Samples are saved as standard wave (.wav) files.
|
||||
- Right-clicking brings up a menu with the applicable items from this list:
|
||||
- **save instrument as .dmp...**: saves the selected instrument in DefleMask format.
|
||||
- **save wavetable as .dmw...**: saves the selected wavetable in DefleMask format.
|
||||
- **save raw wavetable...**: saves the selected wavetable as raw data.
|
||||
- **save raw sample...**: saves the selected sample as raw data.
|
||||
- **Toggle folders/standard view**: Enables (and disables) folder view, explained below.
|
||||
- **Move up**: Moves the currently selected instrument up in the list. Pattern data will automatically be adjusted to match.
|
||||
- **Move down**: Same, but downward.
|
||||
|
|
Before Width: | Height: | Size: 725 B |
Before Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 684 B |
Before Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 406 B |
Before Width: | Height: | Size: 620 B |
|
@ -1,6 +1,35 @@
|
|||
# UI components
|
||||
|
||||
the user interface consists of several components. this paper describes some of them.
|
||||
the user interface consists of several kinds of components, some of which benefit from explanation.
|
||||
|
||||
## text fields
|
||||
|
||||
text fields are able to hold... text.
|
||||
|
||||
click on a text field to start editing, and click away to stop editing.
|
||||
|
||||
the following keyboard shortcuts work while on a text field:
|
||||
|
||||
- `Ctrl-X`: cut
|
||||
- `Ctrl-C`: copy
|
||||
- `Ctrl-V`: paste
|
||||
- `Ctrl-A`: select all
|
||||
|
||||
(replace Ctrl with Command on macOS)
|
||||
|
||||
## number input fields
|
||||
|
||||
these work similar to text fields, but you may only input numbers.
|
||||
|
||||
they also usually have `+` and `-` buttons which allow you to increase/decrease the amount when clicked (and rapidly do so when click-holding).
|
||||
|
||||
## sliders
|
||||
|
||||
sliders are used for controlling values in a quick manner by being dragged.
|
||||
|
||||
using the scroll wheel while holding Ctrl will change the slider's value by small amounts.
|
||||
|
||||
right-clicking or Ctrl-clicking or a slider (Command-click on macOS) will turn it into a number input field for a short period of time, allowing you to input precise values.
|
||||
|
||||
## windows
|
||||
|
||||
|
@ -16,7 +45,7 @@ to resize a window, drag the bottom right corner (marked by a triangular tab) or
|
|||
to collapse a window, click on the triangle in the title bar.
|
||||
clicking again expands it.
|
||||
|
||||
to close a window, click on the `X` at the top right corner.
|
||||
to close a window, click on the `X` at the top right corner, or select it from the "window" menu.
|
||||
|
||||
### arrangement and docking
|
||||
|
||||
|
@ -55,30 +84,3 @@ selecting this option will hide the tab bar of that window.
|
|||
to bring it back, click on the top left corner.
|
||||
|
||||
to undock a window, drag its tab away from where it is docked. then it will be floating again.
|
||||
|
||||
## text fields
|
||||
|
||||
text fields are able to hold... text.
|
||||
|
||||
click on a text field to start editing, and click away to stop editing.
|
||||
|
||||
the following keyboard shortcuts work while on a text field:
|
||||
|
||||
- `Ctrl-X`: cut
|
||||
- `Ctrl-C`: copy
|
||||
- `Ctrl-V`: paste
|
||||
- `Ctrl-A`: select all
|
||||
|
||||
(replace Ctrl with Command on macOS)
|
||||
|
||||
## number input fields
|
||||
|
||||
these work similar to text fields, but you may only input numbers.
|
||||
|
||||
they also usually have two buttons which allow you to increase/decrease the amount when clicked (and rapidly do so when click-holding).
|
||||
|
||||
## sliders
|
||||
|
||||
sliders are used for controlling values in a quick manner by being dragged.
|
||||
|
||||
alternatively, right-clicking or Ctrl-clicking or a slider (Command-click on macOS) will turn it into a number input field for a short period of time, allowing you to input fine values.
|
||||
|
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 436 KiB |
Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 435 KiB |
|
@ -6,10 +6,10 @@ items in _italics_ don't appear in basic mode and are only available in advanced
|
|||
|
||||
# file
|
||||
|
||||
- **new...**: create a new song.
|
||||
- **new...**: creates a new song.
|
||||
- **open...**: opens the file picker, allowing you to select a song to open.
|
||||
- **open recent**: contains a list of the songs you've opened before.
|
||||
- **clear history**: this option erases the file history.
|
||||
- **clear history**: erases the file history.
|
||||
|
||||
- **save**: saves the current song.
|
||||
- opens the file picker if this is a new song, or a backup.
|
||||
|
@ -33,9 +33,9 @@ items in _italics_ don't appear in basic mode and are only available in advanced
|
|||
- Neo Geo CD (DefleMask 1.0+)
|
||||
- only use this option if you really need it. there are features which DefleMask does not support, like some effects and FM macros, so these will be lost.
|
||||
|
||||
- **export audio...**: export your song to a .wav file. see next section for more details.
|
||||
- **export VGM...**: export your song to a .vgm file. see next section for more details.
|
||||
- **export ZSM...**: export your song to a .zsm file. see next section for more details.
|
||||
- **export audio...**: opens the file picker, allowing you to export your song to a .wav file. see next section for more details.
|
||||
- **export VGM...**: opens the file picker, allowing you to export your song to a .vgm file. see next section for more details.
|
||||
- **export ZSM...**: opens the file picker, allowing you to export your song to a .zsm file. see next section for more details.
|
||||
- only available when there's a YM2151 and/or VERA.
|
||||
- **export command stream...**: export song data to a command stream file. see next section for more details.
|
||||
- this option is for developers.
|
||||
|
@ -48,7 +48,7 @@ items in _italics_ don't appear in basic mode and are only available in advanced
|
|||
- _**remove chip...**_: remove a chip.
|
||||
- **Preserve channel positions**: same thing as above.
|
||||
|
||||
- **restore backup**: restore a previously saved backup.
|
||||
- **restore backup**: restores a previously saved backup.
|
||||
- Furnace keeps up to 5 backups of a song.
|
||||
- the backup directory is located in:
|
||||
- Windows: `%USERPROFILE%\AppData\Roaming\furnace\backups`
|
||||
|
@ -87,8 +87,8 @@ the following settings exist:
|
|||
- other versions may not support all chips.
|
||||
- use this option if you need to export for a quirky player or parser.
|
||||
- for example, RYMCast is picky with format versions. if you're going to use this player, select 1.60.
|
||||
- **loop**: writes loop. if disabled, the resulting file won't loop.
|
||||
- **loop trail**: this option allows you to set how much of the song is written after it loops.
|
||||
- **loop**: includes loop information. if disabled, the resulting file won't loop.
|
||||
- **loop trail**: sets how much of the song is written after it loops.
|
||||
- the reason this exists is to work around a VGM format limitation in where post-loop state isn't recorded at all.
|
||||
- this may change the song length as it appears on a player.
|
||||
- **auto-detect**: detect how much to write automatically.
|
||||
|
@ -122,6 +122,7 @@ the following settings are available:
|
|||
- I suggest you use the same rate as the song's.
|
||||
- apparently ZSM doesn't support changing the rate mid-song.
|
||||
- **loop**: enables loop. if disabled, the song won't loop.
|
||||
- **optimize size**: removes unnecessary commands to reduce size.
|
||||
|
||||
click on **Begin Export** to... you know.
|
||||
|
||||
|
@ -136,6 +137,8 @@ it's not really useful, unless you're a developer and want to use a command stre
|
|||
|
||||
# edit
|
||||
|
||||
- **...**: does nothing except prevent accidental clicks on later menu items if the menu is too tall to fit on the program window.
|
||||
|
||||
- **undo**: reverts the last action.
|
||||
- **redo**: repeats what you undid previously.
|
||||
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
# settings
|
||||
|
||||
settings are saved when clicking the **OK** button at the bottom of the dialog.
|
||||
|
||||
|
||||
settings are saved when clicking the **OK** or **Apply** buttons at the bottom of the dialog.
|
||||
|
||||
## General
|
||||
|
||||
### Program
|
||||
|
||||
- **Render backend**
|
||||
- changing this may help with performace issues.
|
||||
- **Late render clear**
|
||||
- **Render backend**: changing this may help with performace issues.
|
||||
- **Late render clear**: this option is only useful when using old versions of Mesa drivers. it force-waits for VBlank by clearing after present, reducing latency.
|
||||
- **Power-saving mode**: saves power by lowering the frame rate to 2fps when idle.
|
||||
- may cause issues under Mesa drivers!
|
||||
- **Disable threaded input (restart after changing!)**: processes key presses for note preview on a separate thread (on supported platforms), which reduces latency.
|
||||
- however, crashes have been reported when threaded input is on. enable this option if that is the case.
|
||||
- **Enable event delay**
|
||||
- may cause issues with high-polling-rate mice when previewing notes.
|
||||
- **Enable event delay**: may cause issues with high-polling-rate mice when previewing notes.
|
||||
|
||||
### File
|
||||
|
||||
- **Use system file picker**: uses native OS file dialog instead of Furnace's.
|
||||
- **Number of recent files**: number of files to show in the _open recent..._ menu.
|
||||
- **Compress when saving**
|
||||
- uses zlib to compress saved songs.
|
||||
- **Save unused patterns**
|
||||
- **Use new pattern format when saving**
|
||||
- **Don't apply compatibility flags when loading .dmf**
|
||||
- **Number of recent files**: number of files that will be remembered in the _open recent..._ menu.
|
||||
- **Compress when saving**: uses zlib to compress saved songs.
|
||||
- **Save unused patterns**: stores unused patterns in a saved song.
|
||||
- **Use new pattern format when saving**: stores patterns in the new, optimized and smaller format. only disable if you need to work with older versions of Furnace.
|
||||
- **Don't apply compatibility flags when loading .dmf**: does exactly what the option says. your .dmf songs may not play correctly after enabled.
|
||||
- **Play after opening song:**
|
||||
- No
|
||||
- Only if already playing
|
||||
- Yes
|
||||
- **Audio export loop/fade out time:**
|
||||
- **Set to these values on start-up:**
|
||||
- **Loops**: number of additional times to play through `0Bxx` song loop.
|
||||
|
@ -41,11 +40,10 @@ settings are saved when clicking the **OK** button at the bottom of the dialog.
|
|||
- this will not choose a random system at each start.
|
||||
- **Reset to defaults**: sets default to "Sega Genesis/Mega Drive".
|
||||
- **Name**: name for the default system. may be set to any text.
|
||||
- **Configure:**: same as in the [chip manager](../8-advanced/chip-manager.md) and [mixer](../8-advanced/mixer.md).
|
||||
- **Configure**: same as in the [chip manager](../8-advanced/chip-manager.md) and [mixer](../8-advanced/mixer.md).
|
||||
- **When creating new song**:
|
||||
- **Display system preset selector**
|
||||
- **Start with initial system**
|
||||
- **Restart song when changing chip properties**
|
||||
|
||||
### Start-up
|
||||
|
||||
|
@ -70,22 +68,24 @@ settings are saved when clicking the **OK** button at the bottom of the dialog.
|
|||
|
||||
- **Backend**: selects SDL or JACK for audio output.
|
||||
- only appears on Linux, or MacOS compiled with JACK support
|
||||
- **Driver**
|
||||
- **Driver**: select a different SDL audio driver if you're having problems with the default one.
|
||||
- **Device**: audio device for playback.
|
||||
- **Sample rate**
|
||||
- **Outputs**: number of audio outputs created, up to 16.
|
||||
- only appears when Backend is JACK.
|
||||
- **Channels**: number of output channels to use.
|
||||
- **Buffer size**: size of buffer in both samples and milliseconds.
|
||||
- setting this to a low value may cause stuttering/glitches in playback (known as "underruns" or "xruns").
|
||||
- setting this to a high value increases latency.
|
||||
- **Low-latency mode (experimental!)**: reduces latency by running the engine faster than the tick rate. useful for live playback/jam mode.
|
||||
- _warning:_ experimental! may produce glitches. only enable if your buffer size is small (10ms or less).
|
||||
- **Force mono audio**
|
||||
- only enable if your buffer size is small (10ms or less).
|
||||
- **Force mono audio**: use if you're unable to hear stereo audio (e.g. single speaker or hearing loss in one ear).
|
||||
- **want:** displays requested audio configuration.
|
||||
- **got:** displays actual audio configuration returned by audio backend.
|
||||
|
||||
### Mixing
|
||||
|
||||
- **Quality**: selects quality of resampling. low quality reduces CPU load.
|
||||
- **Quality**: selects quality of resampling. low quality reduces CPU load by a small amount.
|
||||
- **Software clipping**: clips output to nominal range (-1.0 to 1.0) before passing it to the audio device.
|
||||
- this avoids activating Windows' built-in limiter.
|
||||
|
||||
|
@ -93,32 +93,39 @@ settings are saved when clicking the **OK** button at the bottom of the dialog.
|
|||
|
||||
- **Metronome volume**
|
||||
|
||||
|
||||
|
||||
## MIDI
|
||||
|
||||
### MIDI input
|
||||
|
||||
- **MIDI input**: input device.
|
||||
- **Note input**
|
||||
- **Velocity input**
|
||||
- **Map MIDI channels to direct channels**
|
||||
- **Map Yamaha FM voice data to instruments**
|
||||
- **Program change is instrument selection**
|
||||
- **Value input style**:
|
||||
- **Disabled/custom**
|
||||
- **Two octaves (0 is C-4, F is D#5)**
|
||||
- **Raw (note number is value)**
|
||||
- **Two octaves alternate (lower keys are 0-9, upper keys are A-F)**
|
||||
- **Use dual control change (one for each nibble)**
|
||||
- **CC of upper nibble**
|
||||
- **CC of lower nibble**
|
||||
- **Use 14-bit control change**
|
||||
- **MSB CC**
|
||||
- **LSB CC**
|
||||
- **Use single control change**
|
||||
- **Control**
|
||||
- **Per-column control change**
|
||||
- **Note input**: enables note input. disable if you intend to use this device only for binding actions.
|
||||
- **Velocity input**: enables velocity input when entering notes in the pattern.
|
||||
- **Map MIDI channels to direct channels**: when enabled, notes from MIDI channels will be mapped to channels rather than the cursor position.
|
||||
- **Map Yamaha FM voice data to instruments**: when enabled, Furnace will listen for any transmitted Yamaha SysEx patches.
|
||||
- this option is only useful if you have a Yamaha FM synthesizer (e.g. TX81Z).
|
||||
- selecting a voice or using the "Voice Transmit?" option will send a patch, and Furnace will create a new instrument with its data.
|
||||
- this may also be triggered by clicking on "Receive from TX81Z" in the instrument editor (OPZ only).
|
||||
- **Program change is instrument selection**: changes the current instrument when a program change event is received.
|
||||
- **Value input style**: changes the way values are entered when the pattern cursor is not in the Note column. the following styles are available:
|
||||
- **Disabled/custom**: no value input through MIDI.
|
||||
- **Two octaves (0 is C-4, F is D#5)**: maps keys in two octaves to single nibble input. the layout is:
|
||||
- ` - octave n -- octave n+1 -`
|
||||
- ` 1 3 6 8 A D F # # # `
|
||||
- `0 2 4 5 7 9 B C E # # # # #`
|
||||
- **Raw (note number is value)**: the note number becomes the input value. not useful if you want to input anything above 7F.
|
||||
- **Two octaves alternate (lower keys are 0-9, upper keys are A-F)**: maps keys in two octaves, but with a different layout:
|
||||
- ` - octave n -- octave n+1 -`
|
||||
- ` A B C D E F # # # # `
|
||||
- `0 1 2 3 4 5 6 7 8 9 # # # #`
|
||||
- **Use dual control change (one for each nibble)**: maps two control change events to the nibbles of a value.
|
||||
- **CC of upper nibble**: select the CC number that will change the upper nibble.
|
||||
- **CC of lower nibble**: select the CC number that will change the lower nibble.
|
||||
- **Use 14-bit control change**: maps two control change events that together form a single 14-bit CC. some MIDI controllers do these.
|
||||
- **MSB CC**: select the CC containing the upper portion of the control.
|
||||
- **LSB CC**: select the CC containing the lower portion of the control.
|
||||
- **Use single control change**: maps one control change event. not useful if you want to input odd numbers.
|
||||
- **Control**: select the CC number that will change the value.
|
||||
- **Per-column control change**: when enabled, you can map several control change events to a channel's columns.
|
||||
- **Instrument**\
|
||||
**Volume**\
|
||||
**Effect `x` type**\
|
||||
|
@ -132,36 +139,34 @@ settings are saved when clicking the **OK** button at the bottom of the dialog.
|
|||
- **LSB CC**
|
||||
- **Use single control change (imprecise)**
|
||||
- **Control**
|
||||
- **Volume curve**
|
||||
- **Actions:**
|
||||
- **Volume curve**: adjust the velocity to volume curve.
|
||||
- **Actions**: this allows you to bind note input and control change events to actions.
|
||||
- **`+`** button: adds a new action.
|
||||
- window-with-arrow button: new action with learning! press a button or move a slider/knob/something on your device.
|
||||
- each action has the following:
|
||||
- **Type**
|
||||
- **Channel**
|
||||
- **Note/Control**
|
||||
- **Velocity/Value**
|
||||
- **Action**
|
||||
- **Learn**
|
||||
- **Remove**
|
||||
- **Type**: type of event.
|
||||
- **Channel**: channel of event.
|
||||
- **Note/Control**: the note/control change number.
|
||||
- **Velocity/Value**: the velocity or control value
|
||||
- **Action**: the GUI action to perform.
|
||||
- **Learn**: after clicking on this button, do something in your MIDI device and Furnace will map that to this action.
|
||||
- **Remove**: remove this action.
|
||||
|
||||
### MIDI output
|
||||
|
||||
- **MIDI output**: output device.
|
||||
- **Output mode:**
|
||||
- **Off (use for TX81Z)**
|
||||
- **Melodic**
|
||||
- **Send Program Change**
|
||||
- **Send MIDI clock**
|
||||
- **Send MIDI timecode**
|
||||
- **Timecode frame rate:**
|
||||
- **Closest to Tick Rate**
|
||||
- **Film (24fps)**
|
||||
- **PAL (25fps)**
|
||||
- **NTSC drop (29.97fps)**
|
||||
- **NTSC non-drop (30fps)**
|
||||
|
||||
|
||||
- **Off (use for TX81Z)**: don't output anything. use if you plan to use Furnace as sync master, or the "Receive from TX81Z" button in the OPZ instrument editor.
|
||||
- **Melodic**: output MIDI events.
|
||||
- **Send Program Change**: output program change events when instrument change commands occur.
|
||||
- **Send MIDI clock**: output MIDI beat clock.
|
||||
- **Send MIDI timecode**: output MIDI timecode.
|
||||
- **Timecode frame rate**: sets the timing standard used for MIDI timecode.
|
||||
- **Closest to Tick Rate**: automatically sets the rate based on the song's Tick Rate.
|
||||
- **Film (24fps)**: output at 24 codes per second.
|
||||
- **PAL (25fps)**: output at 25 codes per second.
|
||||
- **NTSC drop (29.97fps)**: output at ~29.97 codes per second, skipping frames 0 and 1 of each minute that doesn't divide by 10.
|
||||
- **NTSC non-drop (30fps)**: output at 30 codes per second.
|
||||
|
||||
## Emulation
|
||||
|
||||
|
@ -174,17 +179,13 @@ settings are saved when clicking the **OK** button at the bottom of the dialog.
|
|||
**FDS core**\
|
||||
**SID core**\
|
||||
**POKEY core**\
|
||||
**OPN/OPNA/OPNB cores**: all of these are covered in the [guide to choosing emulation cores](../9-guides/emulation-cores.md).
|
||||
**OPN/OPNA/OPNB cores**:
|
||||
- **Playback Core(s)**: core(s) to use for realtime playback.
|
||||
- **Render Core(s)**: core(s) to use for exporting audio.
|
||||
- all of these are covered in the [guide to choosing emulation cores](../9-guides/emulation-cores.md).
|
||||
|
||||
- **PC Speaker strategy**: this is covered in the [PC speaker system doc](../7-systems/pcspkr.md).
|
||||
|
||||
- **Sample ROMs:**
|
||||
- **OPL4 YRW801 path**
|
||||
- **MultiPCM TG100 path**
|
||||
- **MultiPCM MU5 path**
|
||||
|
||||
|
||||
|
||||
## Keyboard
|
||||
|
||||
### Keyboard
|
||||
|
@ -209,6 +210,7 @@ settings are saved when clicking the **OK** button at the bottom of the dialog.
|
|||
- **Allow docking editors**
|
||||
- **Remember window position**: remembers the window's last position on start-up.
|
||||
- **Only allow window movement when clicking on title bar**
|
||||
- **Center pop-up windows**
|
||||
- **Play/edit controls layout:**
|
||||
- **Classic**
|
||||
- **Compact**
|
||||
|
@ -324,6 +326,7 @@ settings are saved when clicking the **OK** button at the bottom of the dialog.
|
|||
- **File path**
|
||||
- **Cursor details or file path**
|
||||
- **Nothing**
|
||||
- **Capitalize menu bar**
|
||||
|
||||
### Orders
|
||||
|
||||
|
@ -396,6 +399,7 @@ settings are saved when clicking the **OK** button at the bottom of the dialog.
|
|||
- **Horizontal instrument list**: when there are more instruments than there is room to display them...
|
||||
- if on, scroll horizontally through multiple columns.
|
||||
- if off, scroll vertically in one long column.
|
||||
- only appears if "Unified instrument/wavetable/sample list" is off.
|
||||
- **Instrument list icon style:**
|
||||
- **None**
|
||||
- **Graphical icons**
|
||||
|
@ -408,10 +412,8 @@ settings are saved when clicking the **OK** button at the bottom of the dialog.
|
|||
|
||||
- **Macro editor layout:**
|
||||
- **Unified**
|
||||
- **Mobile**
|
||||
- **Grid**
|
||||
- **Single (with list)**
|
||||
- **Single (combo box)**
|
||||
- **Use classic macro editor vertical slider**
|
||||
|
||||
### Wave Editor
|
||||
|
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 128 KiB |
|
@ -1,6 +1,16 @@
|
|||
# instrument editor
|
||||
|
||||
every instrument can be renamed and have its type changed.
|
||||
the instrument editor always starts with this section:
|
||||
|
||||
![top of instrument editor](instrument-editor-top.png)
|
||||
|
||||
- top-left numeric dropdown: instrument selector.
|
||||
- folder icon: open an instrument file.
|
||||
- save icon: save current instrument as a file.
|
||||
- right-clicking gives the option to save a .dmp format DefleMask preset.
|
||||
- **Name**: instrument name.
|
||||
- **Type**: the system for which the instrument is intended.
|
||||
- if changed, all applicable settings and macros will remain as they are. numbers will not be adjusted.
|
||||
|
||||
depending on the instrument type, there are many different types of instrument editor:
|
||||
|
||||
|
|
BIN
doc/4-instrument/instrument-editor-top.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
|
@ -27,8 +27,11 @@ Amiga | ≤256 | 256 |
|
|||
|
||||
controls across the top line:
|
||||
- waveform number. the `-` and `+` buttons step through the list.
|
||||
- open.
|
||||
- save.
|
||||
- open. opens a file selector to choose the file to open.
|
||||
- save. opens a file selector to choose the file to save to.
|
||||
- right-clicking brings up a menu:
|
||||
- **save as .dmw...**: saves the selected wavetable in DefleMask format.
|
||||
- **save raw...**: saves the selected wavetable as raw data.
|
||||
- **Steps**: view waveform as discrete blocks.
|
||||
- **Lines**: view waveform as a continuous line.
|
||||
- **Width**: length of the waveform data. maximum is 256.
|
||||
|
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 63 KiB |
|
@ -46,7 +46,7 @@ if you need to use more samples, you may change the sample bank using effect `EB
|
|||
|
||||
due to limitations in some of those sound chips, some restrictions exist:
|
||||
|
||||
- Amiga: maximum frequency is 31,469Hz, but anything over 28,867 will sound glitchy on hardware. sample lengths and loop will be set to an even number, and your sample can't be longer than 131070.
|
||||
- Amiga: maximum frequency is 31469Hz, but anything over 28867 will sound glitchy on hardware. sample lengths and loop will be set to an even number, and your sample can't be longer than 131070.
|
||||
- NES: if on DPCM mode, only a limited selection of frequencies is available, and loop position isn't supported (only entire sample).
|
||||
- SegaPCM: your sample can't be longer than 65535, and the maximum frequency is 31.25KHz.
|
||||
- QSound: your sample can't be longer than 65535, and the loop length shall not be greater than 32767.
|
||||
|
@ -75,11 +75,16 @@ in there, you can modify certain data pertaining to your sample, such as the:
|
|||
|
||||
- top-left drop-down box: sample slot.
|
||||
- **Open**: replaces current sample.
|
||||
- Right-clicking brings up a menu:
|
||||
- **import raw...**: brings up a file selector, then presents a dialog to choose the format of the selected file.
|
||||
- **Save**: saves current sample to disk.
|
||||
- Right-clicking brings up a menu:
|
||||
- **save raw...**: brings up a file selector, then saves the sample as raw data.
|
||||
- **Name**: name in sample list.
|
||||
- button to left of **Info**: collapses and expands the info bar.
|
||||
- **Type**: sample format. only 8-bit and 16-bit PCM samples are editable. selecting a format converts the sample data.
|
||||
- **BRR emphasis**: boosts higher frequencies to compensate for the SNES low-pass filter. should not be enabled for BRR-type samples.
|
||||
- **BRR emphasis**: boosts higher frequencies to compensate for the SNES low-pass filter. should not be enabled for BRR-type samples. only appears when applicable.
|
||||
- **8-bit dither**: applies dithering to samples meant to play back at 8-bit resolution. only appears when applicable.
|
||||
|
||||
- **Rate**: switches to normal rate values.
|
||||
- **Compat Rate**: switches to DefleMask-compatible rate values for sample mapping.
|
||||
|
|
|
@ -13,10 +13,8 @@ the YM2413 is equipped with the following features:
|
|||
|
||||
- 9 channels of 2 operator FM synthesis
|
||||
- a drum/percussion mode, replacing the last 3 voices with 5 rhythm channels, with drum mode tones hard-defined in the chip itself, like FM instruments. only pitch might be altered.
|
||||
|
||||
- drum mode works like following: FM channel 7 is for Kick Drum, which is a normal FM channel but routed through mixer twice for 2× volume, like all drum sounds. FM channel 8 splits to Snare, Drum, and Hi-Hat. Snare Drum is the carrier and it works with a special 1 bit noise generator combined with a square wave, all possible by overriding phase-generator with some different synthesis method. Hi-Hat is the modulator and it works with the noise generator and also the special synthesis. CH9 splits to Top-Cymbal and Tom-Tom, Top-Cymbal is the carrier and only has the special synthesis, while Tom-Tom is basically a 1op wave.
|
||||
- special synthesis mentioned already is: 5 square waves are gathered from 4×, 64× and 128× the pitch of channel 8 and 16× and 64× the pitch of channel 9 and they go through a process where 2 HH bits OR'd together, then 1 HH and 1 TC bit OR'd, then the two TC bits OR'd together, and those 3 results get XOR'd.
|
||||
|
||||
- 1 user-definable patch (this patch can be changed throughout the course of the song)
|
||||
- 15 pre-defined patches which can all be used at the same time
|
||||
- support for ADSR on both the modulator and the carrier
|
||||
|
@ -37,7 +35,7 @@ the YM2413 is equipped with the following features:
|
|||
- `y` is the multiplier.
|
||||
- `18xx`: **toggle drums mode.**
|
||||
- `0` disables it and `1` enables it.
|
||||
- only in drums chip.
|
||||
- only in drums mode.
|
||||
- `19xx`: **set attack of all operators.**
|
||||
- `1Axx`: **set attack of operator 1.**
|
||||
- `1Bxx`: **set attack of operator 2.**
|
||||
|
@ -69,3 +67,8 @@ the YM2413 is equipped with the following features:
|
|||
# info
|
||||
|
||||
this chip uses the [FM (OPLL)](../4-instrument/fm-opll.md) instrument editor.
|
||||
|
||||
## chip options
|
||||
|
||||
- **Ignore top/hi-hat frequency changes**: in drums mode, makes the top/hi-hat channels not write frequency since they share it with snare and tom
|
||||
- **Apply fixed frequency to all drums at once**: sets the frequency of all drums to that of a fixed frequency OPLL drums instrument when one note with it is reached
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# TI SN76489 (e.g. Sega Master System)
|
||||
|
||||
a relatively simple sound chip made by Texas Instruments. a derivative of it is used in Sega's Master System, the predecessor to Genesis.
|
||||
a relatively simple sound chip made by Texas Instruments. a derivative of it is used in Sega's Master System, the predecessor to Genesis. It has three square wave channels and one noise channel... not really.
|
||||
|
||||
Nominal mode of SN76489 has 3 quare wave channels, with noise channel having only 3 preset frequencies to use (absurdly low, very low, low). To use more pitches, one can enable mode, which "steals" pitch data from square wave channel 3. By doing that, SN76489 becomes effectively a 3 channel sound chip. In addition, periodic noise mode can be enabled, with same caveats.
|
||||
|
||||
the original iteration of the SN76489 used in the TI-99/4A computer, the SN94624, could only produce tones as low as 100Hz, and was clocked at 447 KHz. all later versions (such as the one in the Master System and Genesis) had a clock divider but ran on a faster clock... except for the SN76494, which can play notes as low as 13.670 Hz (A -1). consequently, its pitch accuracy for higher notes is compromised.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 165 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 125 KiB |
|
@ -2,7 +2,7 @@
|
|||
|
||||
![comments dialog](comments.png)
|
||||
|
||||
Comments, credits, or any arbitrary text may be entered here.
|
||||
Comments, credits, or any arbitrary text may be entered here.\
|
||||
It has no effect on the song.
|
||||
|
||||
There is no word wrap; long lines must be broken manually with the Enter key.
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 54 KiB |
|
@ -29,10 +29,5 @@ Key layout:
|
|||
- **Standard**: Black keys are 2/3 length.
|
||||
- **Continuous**: Black keys are full length.
|
||||
|
||||
Value input pad: (document this)
|
||||
- **Disabled**
|
||||
- **Replace piano**
|
||||
- **Split (automatic)**
|
||||
- **Split (always visible)**
|
||||
|
||||
**Share play/edit offset/range**: (document this)
|
||||
**Share play/edit offset/range**: If disabled, the piano will keep different octave and range values for playback and non-playback states.
|
||||
**Read-only (can't input notes): Prevents note entry.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
|
@ -4,4 +4,9 @@ here is a small collection of useful tricks and techniques to really make Furnac
|
|||
|
||||
- [using samples with limited playback rates](limited-samples.md)
|
||||
- [choosing emulation cores](emulation-cores.md)
|
||||
- [guide on using OPLL patch macro](opllswitching.md)
|
||||
- [using OPLL patch macro](opllswitching.md)
|
||||
- [using AY/SAA hardware envelope](envelope.md)
|
||||
|
||||
# links
|
||||
|
||||
- [FM Synthesis of Real Instruments](http://www.javelinart.com/FM_Synthesis_of_Real_Instruments.pdf): an in-depth tutorial on creating FM patches from scratch.
|
|
@ -1,36 +1,36 @@
|
|||
# choosing emulation cores
|
||||
|
||||
Furnace achieves the authentic sound of videogame hardware by emulating sound chips accurately as possible, using **emulator cores**. in some cases there are multiple cores to choose from, each with different strengths and weaknesses. here are the major differences between them all.
|
||||
Furnace achieves the authentic sound of videogame hardware by emulating sound chips as accurately as possible, using **emulator cores**. in some cases there are multiple cores to choose from, each with different strengths and weaknesses. here are the major differences between them all.
|
||||
|
||||
- **Arcade/YM2151 core**:
|
||||
- **ymfm**: default. much less CPU usage than Nuked-OPM, but less accurate. recommended for users with last-gen or earlier hardware.
|
||||
- **Nuked-OPM**: much more accurate than ymfm, due to the emulator being based on an image of the die map taken from a real YM2151. very CPU heavy, only recommended for users with recent hardware.
|
||||
- **ymfm**: default playback core. much less CPU usage than Nuked-OPM, but less accurate. recommended for users with last-gen or earlier hardware.
|
||||
- **Nuked-OPM**: default render core. much more accurate than ymfm, due to the emulator being based on an image of the die map taken from a real YM2151. very CPU heavy, only recommended for users with recent hardware.
|
||||
|
||||
- **Genesis/YM2612 core**:
|
||||
- **Nuked-OPN2**: default. a little lighter on the CPU than Nuked-OPM.
|
||||
- **Nuked-OPN2**: default core. a little lighter on the CPU than Nuked-OPM.
|
||||
- **ymfm**: same as ymfm above.
|
||||
|
||||
- **SN76489 core**:
|
||||
- **MAME**: default. less accurate than Nuked, but with lower CPU usage. comes from the MAME emulator project.
|
||||
- **MAME**: default core. less accurate than Nuked, but with lower CPU usage. comes from the MAME emulator project.
|
||||
- **Nuked-PSG Mod**: more accurate, but not by that much. this originally started as an emulator for the YM7101 PSG sound generator, but was modified to emulate the SN7 as the MAME core was deemed unsatisfactory by some.
|
||||
|
||||
- **NES core**:
|
||||
- **puNES**: default. it comes from a dedicated NES emulator.
|
||||
- **puNES**: default core. it comes from a dedicated NES emulator.
|
||||
- **NSFplay**: higher CPU usage than puNES.
|
||||
|
||||
- **FDS core**:
|
||||
- **puNES**: default. lower CPU usage and far less accurate.
|
||||
- **NSFplay**: higher CPU usage and much more accurate.
|
||||
- **puNES**: default playback core. lower CPU usage and far less accurate.
|
||||
- **NSFplay**: default render core. higher CPU usage and much more accurate.
|
||||
|
||||
- **SID core**:
|
||||
- **reSID**: default. a high quality emulation core. somewhat CPU heavy.
|
||||
- **reSIDfp**: improved version of reSID. the most accurate choice. _extremely_ CPU heavy.
|
||||
- **reSID**: default playback core. a high quality emulation core. somewhat CPU heavy.
|
||||
- **reSIDfp**: default render core. improved version of reSID. the most accurate choice. _extremely_ CPU heavy.
|
||||
- **dSID**: a lightweight open-source core used in DefleMask. not so accurate but it's very CPU light.
|
||||
|
||||
- **POKEY core**:
|
||||
- **Atari800 (mzpokeysnd)**: does not emulate two-tone mode.
|
||||
- **ASAP (C++ port)**: default. the sound core used in the ASAP player. most accurate option.
|
||||
- **ASAP (C++ port)**: default core. the sound core used in the ASAP player. most accurate option.
|
||||
|
||||
- **OPN/OPNA/OPNB cores**:
|
||||
- **ymfm only**: lower CPU usage, less accurate FM.
|
||||
- **Nuked-OPN2 (FM) + ymfm (SSG/ADPCM)**: default. more accurate FM at the cost of more CPU load.
|
||||
- **Nuked-OPN2 (FM) + ymfm (SSG/ADPCM)**: default cores. more accurate FM at the cost of more CPU load.
|
||||
|
|
34
doc/9-guides/envelope.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# AY-3-8910 / AY8930 / SAA1099 envelope guide
|
||||
|
||||
The AY-3-8910 programmable sound generator, aside from normal 4-bit volume control, has an hardware volume envelope. This feature that allows for defining the shape of the volume envelope at arbitrary speed according to 8 preset envelope shapes. One may think, what is any upside of hardware envelope? Well, it's somewhat independent of tone/noise generators, and since it goes so high in frequency, it can be used melodically! This guide explains how to make best use of the AY/SAA envelope.
|
||||
|
||||
## AY-3-8910 / AY8930
|
||||
|
||||
In the instrument editor:
|
||||
- Add a single tick to the "Waveform" macro with only `envelope` turned on. This will disable any output, but don't worry.
|
||||
- Add a single tick to the "Envelope" macro and select `enable`.
|
||||
|
||||
If you play a note now, you will hear a very high-pitched squeak. This is because you must set envelope period, which is the frequency at which the hardware envelope runs. You can do it in two ways:
|
||||
- `23xx` and `24xx` effects (envelope coarse and fine period);
|
||||
- `29xx` auto-envelope period effect and macros.
|
||||
|
||||
Auto-envelope works via numerator and denominator. In general, the higher the numerator, the higher the envelope pitch. The higher the denominator, the lower the envelope pitch. Why are there both of these? Because the envelope generator might be used to mask the tone output (i.e. affect the square wave as well). To do it, set the "Waveform" macro values to both `tone` and `envelope`. The higher the denominator value, then the lower the envelope pitch relative to the square wave output, and similarly with the numerator. With the square-and-envelope setting, a lot of wild, detuned synth instruments can be made.
|
||||
|
||||
Back to the hardware envelope itself. Depending on the "Envelope" macro value, different envelope shapes can be obtained. The most basic one, 8, is a sawtooth wave. The `direction` value will invert the envelope, producing the reverse sawtooth. The `alternate` value produces an interesting pseudo-triangular wave, similiar to halved sine. That one can also be reversed. `Hold` option disables the envelope.
|
||||
|
||||
_Warning:_ The envelope pitch resolution is fairly low; at high pitches it will be detuned. Because of this, it's used mostly for bass.
|
||||
|
||||
_Warning_: There is only one hardware envelope generator. You can't use two pitches or two waveforms at once.
|
||||
|
||||
## SAA1099
|
||||
|
||||
SAA envelope works a bit differently. It doesn't have its own pitch; instead, it relies on the channel 2/5 pitch. It also has many more parameters than the AY envelope. To use it:
|
||||
- Go to waveform macro and add a single tick set to 0 (unless you want to have a square wave mask).
|
||||
- Set up an envelope macro. Turn on `enabled`, `loop`, and depending on the desired shape, `cut` and `direction`. `Resolution` will give you higher pitch range than on the AY.
|
||||
- Place two notes in the pattern editor. One in channel 2 will control the envelope pitch. The other in channel 3 can be any note you wish; it's just to enable the envelope output.
|
||||
|
||||
## examples
|
||||
|
||||
- [Demoscene-type Beat by Duccinator](https://www.youtube.com/watch?v=qcBgmpPrlUA)
|
||||
- [Philips SAA1099 Test by Duccinator](https://www.youtube.com/watch?v=IBh2gr09zjs)
|
||||
- [Touhou Kaikidan: Mystic Square title theme by ZUN](https://www.youtube.com/watch?v=tUKei7Pz0Fw): Rare instance of AY envelope used for drums, it can be used to mask the noise generator output too
|
2
extern/fmt
vendored
|
@ -1 +1 @@
|
|||
Subproject commit afbcf1e8eafc5d7f27e29c7397f22521eaa33fac
|
||||
Subproject commit e57ca2e3685b160617d3d95fcd9e789c4e06ca88
|
9
extern/igfd/ImGuiFileDialog.cpp
vendored
|
@ -3880,6 +3880,7 @@ namespace IGFD
|
|||
|
||||
float posY = ImGui::GetCursorPos().y; // height of last bar calc
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
if (!fdFile.puDLGDirectoryMode)
|
||||
ImGui::Text(fileNameString);
|
||||
else // directory chooser
|
||||
|
@ -3913,8 +3914,9 @@ namespace IGFD
|
|||
prFileDialogInternal.puIsOk = true;
|
||||
res = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (!(prFileDialogInternal.puCanWeContinue && notEmpty && fileValid==0)) {
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
||||
if (!notEmpty) {
|
||||
if (prFileDialogInternal.puDLGflags&ImGuiFileDialogFlags_ConfirmOverwrite) {
|
||||
ImGui::SetTooltip("file name is empty");
|
||||
|
@ -3925,7 +3927,11 @@ namespace IGFD
|
|||
ImGui::SetTooltip("we can't continue - this is most likely a bug!");
|
||||
} else switch (fileValid) {
|
||||
case 1:
|
||||
#ifdef _WIN32
|
||||
ImGui::SetTooltip("invalid characters in file name\nmake sure there aren't any of these:\n < > : \" / \\ | ? *");
|
||||
#else
|
||||
ImGui::SetTooltip("invalid characters in file name\nmake sure there aren't any slashes (/)");
|
||||
#endif
|
||||
break;
|
||||
case 2:
|
||||
ImGui::SetTooltip("this file name is reserved by the system");
|
||||
|
@ -3939,7 +3945,6 @@ namespace IGFD
|
|||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
|
|
48
extern/igfd/dirent/dirent.h
vendored
|
@ -1114,11 +1114,20 @@ static int u8to16s(wchar_t* dest, const char* src, size_t limit) {
|
|||
int ch, p;
|
||||
char chs;
|
||||
p=0;
|
||||
while (src[p]!=0 && ret<limit) {
|
||||
while (src[p]!=0 && ret<limit-1) {
|
||||
ch=decodeUTF8s((const unsigned char*)&src[p],&chs);
|
||||
dest[ret++]=(unsigned short)ch;
|
||||
// surrogates
|
||||
if (ch>=0x10000) {
|
||||
ch-=0x10000;
|
||||
if (ret+1>=limit-1) break;
|
||||
dest[ret++]=(unsigned short)(0xd800|((ch>>10)&0x3ff));
|
||||
dest[ret++]=(unsigned short)(0xdc00|(ch&0x3ff));
|
||||
} else if (ch<0xd800 || ch>0xdfff) {
|
||||
dest[ret++]=(unsigned short)ch;
|
||||
}
|
||||
p+=chs;
|
||||
}
|
||||
dest[ret]=0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1176,19 +1185,34 @@ dirent_mbstowcs_s(
|
|||
|
||||
static int u16to8s(char* dest, const wchar_t* src, size_t limit) {
|
||||
size_t ret=0;
|
||||
unsigned int next=0;
|
||||
for (; (*src)!=0; src++) {
|
||||
if ((*src)<0x80) {
|
||||
if (ret+1>=limit-1) break;
|
||||
dest[ret++]=(*src);
|
||||
} else if ((*src)<0x800) {
|
||||
if (ret+2>=limit-1) break;
|
||||
dest[ret++]=(0xc0+(((*src)>>6)&31));
|
||||
dest[ret++]=(0x80+((*src)&63));
|
||||
if ((*src)>=0xd800 && (*src)<0xdc00) {
|
||||
next=0x10000+(((*src)&0x3ff)<<10);
|
||||
continue;
|
||||
} else if ((*src)>=0xdc00 && (*src)<0xe000) {
|
||||
next|=(*src)&0x3ff;
|
||||
} else {
|
||||
next=(*src);
|
||||
}
|
||||
if (next<0x80) {
|
||||
if (ret+1>=limit-1) break;
|
||||
dest[ret++]=next;
|
||||
} else if (next<0x800) {
|
||||
if (ret+2>=limit-1) break;
|
||||
dest[ret++]=(0xc0+((next>>6)&31));
|
||||
dest[ret++]=(0x80+(next&63));
|
||||
} else if (next<0x10000) {
|
||||
if (ret+3>=limit-1) break;
|
||||
dest[ret++]=(0xe0+(((*src)>>12)&15));
|
||||
dest[ret++]=(0x80+(((*src)>>6)&63));
|
||||
dest[ret++]=(0x80+((*src)&63));
|
||||
dest[ret++]=(0xe0+((next>>12)&15));
|
||||
dest[ret++]=(0x80+((next>>6)&63));
|
||||
dest[ret++]=(0x80+(next&63));
|
||||
} else {
|
||||
if (ret+4>=limit-1) break;
|
||||
dest[ret++]=(0xf0+((next>>18)&7));
|
||||
dest[ret++]=(0x80+((next>>12)&63));
|
||||
dest[ret++]=(0x80+((next>>6)&63));
|
||||
dest[ret++]=(0x80+(next&63));
|
||||
}
|
||||
}
|
||||
dest[ret]=0;
|
||||
|
|
17
extern/nfd-modified/src/nfd_win.cpp
vendored
|
@ -61,7 +61,7 @@ class NFDWinEvents: public IFileDialogEvents {
|
|||
return ret;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP OnFileOk(IFileDialog*) { return E_NOTIMPL; }
|
||||
IFACEMETHODIMP OnFileOk(IFileDialog*) { return S_OK; }
|
||||
IFACEMETHODIMP OnFolderChange(IFileDialog*) { return E_NOTIMPL; }
|
||||
IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return E_NOTIMPL; }
|
||||
IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) { return E_NOTIMPL; }
|
||||
|
@ -677,6 +677,21 @@ nfdresult_t NFD_SaveDialog( const std::vector<std::string>& filterList,
|
|||
goto end;
|
||||
}
|
||||
|
||||
// Set a flag for no history
|
||||
DWORD dwFlags;
|
||||
result = fileSaveDialog->GetOptions(&dwFlags);
|
||||
if ( !SUCCEEDED(result) )
|
||||
{
|
||||
NFDi_SetError("Could not get options.");
|
||||
goto end;
|
||||
}
|
||||
result = fileSaveDialog->SetOptions(dwFlags | FOS_DONTADDTORECENT);
|
||||
if ( !SUCCEEDED(result) )
|
||||
{
|
||||
NFDi_SetError("Could not set options.");
|
||||
goto end;
|
||||
}
|
||||
|
||||
// Show the dialog.
|
||||
result = fileSaveDialog->Show(NULL);
|
||||
if ( SUCCEEDED(result) )
|
||||
|
|
|
@ -67,7 +67,7 @@ void k007232_core::voice_t::tick(u8 ne)
|
|||
}
|
||||
}
|
||||
|
||||
m_out = s8(m_data) - 0x40; // send to output (ASD/BSD) pin
|
||||
m_out = s8(m_data&0x7f) - 0x40; // send to output (ASD/BSD) pin
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@ when copying pattern data from Furnace, it's stored in the clipboard as plain te
|
|||
org.tildearrow.furnace - Pattern Data (144)
|
||||
```
|
||||
|
||||
this top line of text is always the same except for the number in parentheses, which is the internal build number. for example, 0.6pre7 is `162`.
|
||||
this top line of text is always the same except for the number in parentheses, which is the internal build number. for example, 0.6pre9 is `169`.
|
||||
|
||||
the second line is a number between 0 and 18 (decimal) which indicates which column the clip starts from.
|
||||
- `0`: note.
|
||||
|
|
|
@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
|
|||
|
||||
the format versions are:
|
||||
|
||||
- 169: Furnace 0.6pre9
|
||||
- 166: Furnace 0.6pre8
|
||||
- 162: Furnace 0.6pre7
|
||||
- 161: Furnace 0.6pre6
|
||||
- 158: Furnace 0.6pre5
|
||||
|
@ -220,7 +222,7 @@ size | description
|
|||
| - 0xcc: K053260 - 4 channels
|
||||
| - 0xcd: TED - 2 channels
|
||||
| - 0xce: Namco C140 - 24 channels
|
||||
| - 0xcf: Namco C219 - 16 channels (UNAVAILABLE)
|
||||
| - 0xcf: Namco C219 - 16 channels
|
||||
| - 0xd0: Namco C352 - 32 channels (UNAVAILABLE)
|
||||
| - 0xde: YM2610B extended - 19 channels
|
||||
| - 0xe0: QSound - 19 channels
|
||||
|
@ -347,7 +349,8 @@ size | description
|
|||
--- | **a couple more compat flags** (>=138)
|
||||
1 | broken portamento during legato
|
||||
1 | broken macro during note off in some FM chips (>=155)
|
||||
6 | reserved
|
||||
1 | pre note (C64) does not compensate for portamento or legato (>=168)
|
||||
5 | reserved
|
||||
--- | **speed pattern of first song** (>=139)
|
||||
1 | length of speed pattern (fail if this is lower than 0 or higher than 16)
|
||||
16 | speed pattern (this overrides speed 1 and speed 2 settings)
|
||||
|
|
|
@ -120,6 +120,7 @@ the following instrument types are available:
|
|||
- 50: K053260
|
||||
- 52: TED
|
||||
- 53: C140
|
||||
- 54: C219
|
||||
|
||||
the following feature codes are recognized:
|
||||
|
||||
|
|
|
@ -15,17 +15,17 @@
|
|||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLongVersionString</key>
|
||||
<string>0.6pre7</string>
|
||||
<string>0.6pre9</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Furnace</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.6pre7</string>
|
||||
<string>0.6pre9</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.6pre7</string>
|
||||
<string>0.6pre9</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string></string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
|
|
2059
res/icons.sfd
BIN
res/icons.ttf
5
res/releaseReadme/stable-linux.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Furnace (chiptune tracker)
|
||||
|
||||
thank you for downloading Furnace! I hope you enjoy using it.
|
||||
|
||||
extract this archive, and run `furnace` to get started.
|
15
res/releaseReadme/stable-mac.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Furnace (chiptune tracker)
|
||||
|
||||
thank you for downloading Furnace! I hope you enjoy using it.
|
||||
|
||||
move Furnace to Applications (or some other place).
|
||||
if you are using a recent version of macOS, you may get an error saying that Furnace is damaged.
|
||||
in that case, open Terminal, and type this:
|
||||
|
||||
```
|
||||
xattr -d com.apple.quarantine /path/to/Furnace.app
|
||||
```
|
||||
|
||||
(replace `/path/to/Furnace.app` with the path where Furnace.app is located, e.g. `/Applications/Furnace.app`)
|
||||
|
||||
you may need to reboot after doing this before launching Furnace.
|
5
res/releaseReadme/stable-win.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Furnace (chiptune tracker)
|
||||
|
||||
thank you for downloading Furnace! I hope you enjoy using it.
|
||||
|
||||
extract this archive, and run furnace.exe to get started.
|
6
res/releaseReadme/unstable-other.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
# unstable build notice
|
||||
|
||||
this is a dev build (also known as "unstable", "artifact" or "nightly").
|
||||
it may contain work-in-progress features and/or bug fixes.
|
||||
|
||||
no compatibility is guaranteed between unstable and stable - you have been warned!
|
11
res/releaseReadme/unstable-win.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
# unstable build notice
|
||||
|
||||
this is a dev build (also known as "unstable", "artifact" or "nightly").
|
||||
it may contain work-in-progress features and/or bug fixes.
|
||||
|
||||
no compatibility is guaranteed between unstable and stable - you have been warned!
|
||||
|
||||
# the .pdb file
|
||||
|
||||
YOU SHALL COPY THIS FILE ALONGSIDE furnace.exe. it contains debug information which is vital in the moment of a crash.
|
||||
failure to copy this file will result in useless backtraces (furnace_crash.txt) and therefore hinder me from fixing any crashes.
|
|
@ -14,7 +14,7 @@ fi
|
|||
|
||||
cd linuxbuild
|
||||
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O3" -DCMAKE_CXX_FLAGS="-O3 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O3" -DCMAKE_CXX_FLAGS="-O3 -Wall -Wextra -Wno-unused-parameter -Werror" -DWITH_DEMOS=ON -DWITH_INSTRUMENTS=ON -DWITH_WAVETABLES=ON .. || exit 1
|
||||
make -j4 || exit 1
|
||||
|
||||
cd ..
|
||||
|
|
|
@ -15,7 +15,7 @@ fi
|
|||
cd win32build
|
||||
|
||||
# TODO: potential Arch-ism?
|
||||
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON -DWITH_RENDER_DX11=OFF .. || exit 1
|
||||
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2 -march=i686" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -march=i686" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=OFF -DWITH_RENDER_DX11=ON .. || exit 1
|
||||
make -j8 || exit 1
|
||||
|
||||
cd ..
|
||||
|
|
46
scripts/release-winxp.sh
Executable file
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
# make Windows release
|
||||
# this script shall be run from Arch Linux with MinGW installed!
|
||||
|
||||
if [ ! -e /tmp/furnace ]; then
|
||||
ln -s "$PWD" /tmp/furnace || exit 1
|
||||
fi
|
||||
|
||||
cd /tmp/furnace
|
||||
|
||||
if [ ! -e xpbuild ]; then
|
||||
mkdir xpbuild || exit 1
|
||||
fi
|
||||
|
||||
cd xpbuild
|
||||
|
||||
# TODO: potential Arch-ism?
|
||||
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON -DWITH_RENDER_DX11=OFF .. || exit 1
|
||||
make -j8 || exit 1
|
||||
|
||||
cd ..
|
||||
|
||||
mkdir -p release/winxp || exit 1
|
||||
cd release/winxp
|
||||
|
||||
cp ../../LICENSE LICENSE.txt || exit 1
|
||||
cp ../../xpbuild/furnace.exe . || exit 1
|
||||
cp ../../README.md README.txt || exit 1
|
||||
cp -r ../../papers papers || exit 1
|
||||
cp -r ../../doc doc || exit 1
|
||||
cp -r ../../demos demos || exit 1
|
||||
cp -r ../../instruments instruments || exit 1
|
||||
cp -r ../../wavetables wavetables || exit 1
|
||||
|
||||
i686-w64-mingw32-strip -s furnace.exe || exit 1
|
||||
|
||||
# patch to remove GetTickCount64
|
||||
xxd -c 256 -ps furnace.exe | sed "s/4765745469636b436f756e743634/4765745469636b436f756e740000/g" | xxd -ps -r > furnace-patched.exe
|
||||
rm furnace.exe
|
||||
mv furnace-patched.exe furnace.exe
|
||||
|
||||
zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers doc demos instruments wavetables
|
||||
|
||||
furName=$(git describe --tags | sed "s/v0/0/")
|
||||
|
||||
mv furnace.zip furnace-"$furName"-win32-XP-ONLY.zip
|
|
@ -127,6 +127,7 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) {
|
|||
ac.callback=taSDLProcess;
|
||||
ac.userdata=this;
|
||||
|
||||
logV("opening audio device...");
|
||||
ai=SDL_OpenAudioDevice(request.deviceName.empty()?NULL:request.deviceName.c_str(),0,&ac,&ar,0);
|
||||
if (ai==0) {
|
||||
logE("could not open audio device: %s",SDL_GetError());
|
||||
|
@ -147,6 +148,8 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) {
|
|||
desc.bufsize=ar.samples;
|
||||
desc.fragments=1;
|
||||
|
||||
logV("got info: %d channels, %d bufsize",desc.outChans,desc.bufsize);
|
||||
|
||||
if (desc.outChans>0) {
|
||||
outBufs=new float*[desc.outChans];
|
||||
for (int i=0; i<desc.outChans; i++) {
|
||||
|
|
540
src/engine/cmdStreamOps.cpp
Normal file
|
@ -0,0 +1,540 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
|
||||
#define WRITE_TICK(x) \
|
||||
if (binary) { \
|
||||
if (!wroteTick[x]) { \
|
||||
wroteTick[x]=true; \
|
||||
if (tick-lastTick[x]>255) { \
|
||||
chanStream[x]->writeC(0xfc); \
|
||||
chanStream[x]->writeS(tick-lastTick[x]); \
|
||||
} else if (tick-lastTick[x]>1) { \
|
||||
delayPopularity[tick-lastTick[x]]++; \
|
||||
chanStream[x]->writeC(0xfd); \
|
||||
chanStream[x]->writeC(tick-lastTick[x]); \
|
||||
} else if (tick-lastTick[x]>0) { \
|
||||
chanStream[x]->writeC(0xfe); \
|
||||
} \
|
||||
lastTick[x]=tick; \
|
||||
} \
|
||||
} else { \
|
||||
if (!wroteTickGlobal) { \
|
||||
wroteTickGlobal=true; \
|
||||
w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \
|
||||
} \
|
||||
}
|
||||
|
||||
void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
if (c.value==DIV_NOTE_NULL) {
|
||||
w->writeC(0xb4);
|
||||
} else {
|
||||
w->writeC(CLAMP(c.value+60,0,0xb3));
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
case DIV_CMD_PANNING:
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
case DIV_CMD_HINT_VIBRATO:
|
||||
case DIV_CMD_HINT_VIBRATO_RANGE:
|
||||
case DIV_CMD_HINT_VIBRATO_SHAPE:
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
case DIV_CMD_HINT_ARPEGGIO:
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
w->writeC((unsigned char)c.cmd+0xb4);
|
||||
break;
|
||||
default:
|
||||
w->writeC(0xf0); // unoptimized extended command
|
||||
w->writeC(c.cmd);
|
||||
break;
|
||||
}
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
if (c.value==DIV_NOTE_NULL) {
|
||||
w->writeC(0xff);
|
||||
} else {
|
||||
w->writeC(c.value+60);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_ON:
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
case DIV_CMD_HINT_VIBRATO_RANGE:
|
||||
case DIV_CMD_HINT_VIBRATO_SHAPE:
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
case DIV_CMD_HINT_VOLUME:
|
||||
w->writeC(c.value);
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
case DIV_CMD_HINT_VIBRATO:
|
||||
case DIV_CMD_HINT_ARPEGGIO:
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
w->writeC(c.value);
|
||||
w->writeC(c.value2);
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
w->writeC((c.value?0x80:0)|(c.value2?0x40:0));
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
w->writeS(c.value);
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
case DIV_CMD_SAMPLE_DIR:
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
case DIV_CMD_FM_LFO:
|
||||
case DIV_CMD_FM_LFO_WAVE:
|
||||
case DIV_CMD_FM_FB:
|
||||
case DIV_CMD_FM_EXTCH:
|
||||
case DIV_CMD_FM_AM_DEPTH:
|
||||
case DIV_CMD_FM_PM_DEPTH:
|
||||
case DIV_CMD_STD_NOISE_FREQ:
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
case DIV_CMD_WAVE:
|
||||
case DIV_CMD_GB_SWEEP_TIME:
|
||||
case DIV_CMD_GB_SWEEP_DIR:
|
||||
case DIV_CMD_PCE_LFO_MODE:
|
||||
case DIV_CMD_PCE_LFO_SPEED:
|
||||
case DIV_CMD_NES_DMC:
|
||||
case DIV_CMD_C64_CUTOFF:
|
||||
case DIV_CMD_C64_RESONANCE:
|
||||
case DIV_CMD_C64_FILTER_MODE:
|
||||
case DIV_CMD_C64_RESET_TIME:
|
||||
case DIV_CMD_C64_RESET_MASK:
|
||||
case DIV_CMD_C64_FILTER_RESET:
|
||||
case DIV_CMD_C64_DUTY_RESET:
|
||||
case DIV_CMD_C64_EXTENDED:
|
||||
case DIV_CMD_AY_ENVELOPE_SET:
|
||||
case DIV_CMD_AY_ENVELOPE_LOW:
|
||||
case DIV_CMD_AY_ENVELOPE_HIGH:
|
||||
case DIV_CMD_AY_ENVELOPE_SLIDE:
|
||||
case DIV_CMD_AY_NOISE_MASK_AND:
|
||||
case DIV_CMD_AY_NOISE_MASK_OR:
|
||||
case DIV_CMD_AY_AUTO_ENVELOPE:
|
||||
case DIV_CMD_FDS_MOD_DEPTH:
|
||||
case DIV_CMD_FDS_MOD_HIGH:
|
||||
case DIV_CMD_FDS_MOD_LOW:
|
||||
case DIV_CMD_FDS_MOD_POS:
|
||||
case DIV_CMD_FDS_MOD_WAVE:
|
||||
case DIV_CMD_SAA_ENVELOPE:
|
||||
case DIV_CMD_AMIGA_FILTER:
|
||||
case DIV_CMD_AMIGA_AM:
|
||||
case DIV_CMD_AMIGA_PM:
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
case DIV_CMD_MACRO_ON:
|
||||
case DIV_CMD_HINT_ARP_TIME:
|
||||
w->writeC(1); // length
|
||||
w->writeC(c.value);
|
||||
break;
|
||||
case DIV_CMD_FM_TL:
|
||||
case DIV_CMD_FM_AM:
|
||||
case DIV_CMD_FM_AR:
|
||||
case DIV_CMD_FM_DR:
|
||||
case DIV_CMD_FM_SL:
|
||||
case DIV_CMD_FM_D2R:
|
||||
case DIV_CMD_FM_RR:
|
||||
case DIV_CMD_FM_DT:
|
||||
case DIV_CMD_FM_DT2:
|
||||
case DIV_CMD_FM_RS:
|
||||
case DIV_CMD_FM_KSR:
|
||||
case DIV_CMD_FM_VIB:
|
||||
case DIV_CMD_FM_SUS:
|
||||
case DIV_CMD_FM_WS:
|
||||
case DIV_CMD_FM_SSG:
|
||||
case DIV_CMD_FM_REV:
|
||||
case DIV_CMD_FM_EG_SHIFT:
|
||||
case DIV_CMD_FM_MULT:
|
||||
case DIV_CMD_FM_FINE:
|
||||
case DIV_CMD_AY_IO_WRITE:
|
||||
case DIV_CMD_AY_AUTO_PWM:
|
||||
case DIV_CMD_SURROUND_PANNING:
|
||||
w->writeC(2); // length
|
||||
w->writeC(c.value);
|
||||
w->writeC(c.value2);
|
||||
break;
|
||||
case DIV_CMD_C64_FINE_DUTY:
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
case DIV_CMD_LYNX_LFSR_LOAD:
|
||||
w->writeC(2); // length
|
||||
w->writeS(c.value);
|
||||
break;
|
||||
case DIV_CMD_FM_FIXFREQ:
|
||||
w->writeC(2); // length
|
||||
w->writeS((c.value<<12)|(c.value2&0x7ff));
|
||||
break;
|
||||
case DIV_CMD_NES_SWEEP:
|
||||
w->writeC(1); // length
|
||||
w->writeC((c.value?8:0)|(c.value2&0x77));
|
||||
break;
|
||||
default:
|
||||
logW("unimplemented command %s!",cmdName[c.cmd]);
|
||||
w->writeC(0); // length
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SafeWriter* DivEngine::saveCommand(bool binary) {
|
||||
stop();
|
||||
repeatPattern=false;
|
||||
shallStop=false;
|
||||
setOrder(0);
|
||||
BUSY_BEGIN_SOFT;
|
||||
// determine loop point
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
walkSong(loopOrder,loopRow,loopEnd);
|
||||
logI("loop point: %d %d",loopOrder,loopRow);
|
||||
|
||||
int cmdPopularity[256];
|
||||
int delayPopularity[256];
|
||||
|
||||
int sortedCmdPopularity[16];
|
||||
int sortedDelayPopularity[16];
|
||||
unsigned char sortedCmd[16];
|
||||
unsigned char sortedDelay[16];
|
||||
|
||||
SafeWriter* chanStream[DIV_MAX_CHANS];
|
||||
unsigned int chanStreamOff[DIV_MAX_CHANS];
|
||||
bool wroteTick[DIV_MAX_CHANS];
|
||||
|
||||
memset(cmdPopularity,0,256*sizeof(int));
|
||||
memset(delayPopularity,0,256*sizeof(int));
|
||||
memset(chanStream,0,DIV_MAX_CHANS*sizeof(void*));
|
||||
memset(chanStreamOff,0,DIV_MAX_CHANS*sizeof(unsigned int));
|
||||
memset(sortedCmdPopularity,0,16*sizeof(int));
|
||||
memset(sortedDelayPopularity,0,16*sizeof(int));
|
||||
memset(sortedCmd,0,16);
|
||||
memset(sortedDelay,0,16);
|
||||
|
||||
SafeWriter* w=new SafeWriter;
|
||||
w->init();
|
||||
|
||||
// write header
|
||||
if (binary) {
|
||||
w->write("FCS",4);
|
||||
w->writeI(chans);
|
||||
// offsets
|
||||
for (int i=0; i<chans; i++) {
|
||||
chanStream[i]=new SafeWriter;
|
||||
chanStream[i]->init();
|
||||
w->writeI(0);
|
||||
}
|
||||
// preset delays and speed dial
|
||||
for (int i=0; i<32; i++) {
|
||||
w->writeC(0);
|
||||
}
|
||||
} else {
|
||||
w->writeText("# Furnace Command Stream\n\n");
|
||||
|
||||
w->writeText("[Information]\n");
|
||||
w->writeText(fmt::sprintf("name: %s\n",song.name));
|
||||
w->writeText(fmt::sprintf("author: %s\n",song.author));
|
||||
w->writeText(fmt::sprintf("category: %s\n",song.category));
|
||||
w->writeText(fmt::sprintf("system: %s\n",song.systemName));
|
||||
|
||||
w->writeText("\n");
|
||||
|
||||
w->writeText("[SubSongInformation]\n");
|
||||
w->writeText(fmt::sprintf("name: %s\n",curSubSong->name));
|
||||
w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz));
|
||||
|
||||
w->writeText("\n");
|
||||
|
||||
w->writeText("[SysDefinition]\n");
|
||||
// TODO
|
||||
|
||||
w->writeText("\n");
|
||||
}
|
||||
|
||||
// play the song ourselves
|
||||
bool done=false;
|
||||
playSub(false);
|
||||
|
||||
if (!binary) {
|
||||
w->writeText("[Stream]\n");
|
||||
}
|
||||
int tick=0;
|
||||
bool oldCmdStreamEnabled=cmdStreamEnabled;
|
||||
cmdStreamEnabled=true;
|
||||
double curDivider=divider;
|
||||
int lastTick[DIV_MAX_CHANS];
|
||||
|
||||
memset(lastTick,0,DIV_MAX_CHANS*sizeof(int));
|
||||
while (!done) {
|
||||
if (nextTick(false,true) || !playing) {
|
||||
done=true;
|
||||
}
|
||||
// get command stream
|
||||
bool wroteTickGlobal=false;
|
||||
memset(wroteTick,0,DIV_MAX_CHANS*sizeof(bool));
|
||||
if (curDivider!=divider) {
|
||||
curDivider=divider;
|
||||
WRITE_TICK(0);
|
||||
if (binary) {
|
||||
chanStream[0]->writeC(0xfb);
|
||||
chanStream[0]->writeI((int)(curDivider*65536));
|
||||
} else {
|
||||
w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider));
|
||||
}
|
||||
}
|
||||
for (DivCommand& i: cmdStream) {
|
||||
switch (i.cmd) {
|
||||
// strip away hinted/useless commands
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA:
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
break;
|
||||
default:
|
||||
WRITE_TICK(i.chan);
|
||||
if (binary) {
|
||||
cmdPopularity[i.cmd]++;
|
||||
writePackedCommandValues(chanStream[i.chan],i);
|
||||
} else {
|
||||
w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
cmdStream.clear();
|
||||
tick++;
|
||||
}
|
||||
cmdStreamEnabled=oldCmdStreamEnabled;
|
||||
|
||||
if (binary) {
|
||||
int sortCand=-1;
|
||||
int sortPos=0;
|
||||
while (sortPos<16) {
|
||||
sortCand=-1;
|
||||
for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) {
|
||||
if (cmdPopularity[i]) {
|
||||
if (sortCand==-1) {
|
||||
sortCand=i;
|
||||
} else if (cmdPopularity[sortCand]<cmdPopularity[i]) {
|
||||
sortCand=i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sortCand==-1) break;
|
||||
|
||||
sortedCmdPopularity[sortPos]=cmdPopularity[sortCand];
|
||||
sortedCmd[sortPos]=sortCand;
|
||||
cmdPopularity[sortCand]=0;
|
||||
sortPos++;
|
||||
}
|
||||
|
||||
sortCand=-1;
|
||||
sortPos=0;
|
||||
while (sortPos<16) {
|
||||
sortCand=-1;
|
||||
for (int i=0; i<256; i++) {
|
||||
if (delayPopularity[i]) {
|
||||
if (sortCand==-1) {
|
||||
sortCand=i;
|
||||
} else if (delayPopularity[sortCand]<delayPopularity[i]) {
|
||||
sortCand=i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sortCand==-1) break;
|
||||
|
||||
sortedDelayPopularity[sortPos]=delayPopularity[sortCand];
|
||||
sortedDelay[sortPos]=sortCand;
|
||||
delayPopularity[sortCand]=0;
|
||||
sortPos++;
|
||||
}
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
chanStream[i]->writeC(0xff);
|
||||
// optimize stream
|
||||
SafeWriter* oldStream=chanStream[i];
|
||||
SafeReader* reader=oldStream->toReader();
|
||||
chanStream[i]=new SafeWriter;
|
||||
chanStream[i]->init();
|
||||
|
||||
while (1) {
|
||||
try {
|
||||
unsigned char next=reader->readC();
|
||||
switch (next) {
|
||||
case 0xb8: // instrument
|
||||
case 0xc0: // pre porta
|
||||
case 0xc3: // vibrato range
|
||||
case 0xc4: // vibrato shape
|
||||
case 0xc5: // pitch
|
||||
case 0xc7: // volume
|
||||
case 0xca: // legato
|
||||
chanStream[i]->writeC(next);
|
||||
next=reader->readC();
|
||||
chanStream[i]->writeC(next);
|
||||
break;
|
||||
case 0xbe: // panning
|
||||
case 0xc2: // vibrato
|
||||
case 0xc6: // arpeggio
|
||||
case 0xc8: // vol slide
|
||||
case 0xc9: // porta
|
||||
chanStream[i]->writeC(next);
|
||||
next=reader->readC();
|
||||
chanStream[i]->writeC(next);
|
||||
next=reader->readC();
|
||||
chanStream[i]->writeC(next);
|
||||
break;
|
||||
case 0xf0: { // full command (pre)
|
||||
unsigned char cmd=reader->readC();
|
||||
bool foundShort=false;
|
||||
for (int j=0; j<16; j++) {
|
||||
if (sortedCmd[j]==cmd) {
|
||||
chanStream[i]->writeC(0xd0+j);
|
||||
foundShort=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundShort) {
|
||||
chanStream[i]->writeC(0xf7); // full command
|
||||
chanStream[i]->writeC(cmd);
|
||||
}
|
||||
|
||||
unsigned char cmdLen=reader->readC();
|
||||
logD("cmdLen: %d",cmdLen);
|
||||
for (unsigned char j=0; j<cmdLen; j++) {
|
||||
next=reader->readC();
|
||||
chanStream[i]->writeC(next);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xfb: // tick rate
|
||||
chanStream[i]->writeC(next);
|
||||
next=reader->readC();
|
||||
chanStream[i]->writeC(next);
|
||||
next=reader->readC();
|
||||
chanStream[i]->writeC(next);
|
||||
next=reader->readC();
|
||||
chanStream[i]->writeC(next);
|
||||
next=reader->readC();
|
||||
chanStream[i]->writeC(next);
|
||||
break;
|
||||
case 0xfc: { // 16-bit wait
|
||||
unsigned short delay=reader->readS();
|
||||
bool foundShort=false;
|
||||
for (int j=0; j<16; j++) {
|
||||
if (sortedDelay[j]==delay) {
|
||||
chanStream[i]->writeC(0xe0+j);
|
||||
foundShort=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundShort) {
|
||||
chanStream[i]->writeC(next);
|
||||
chanStream[i]->writeS(delay);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xfd: { // 8-bit wait
|
||||
unsigned char delay=reader->readC();
|
||||
bool foundShort=false;
|
||||
for (int j=0; j<16; j++) {
|
||||
if (sortedDelay[j]==delay) {
|
||||
chanStream[i]->writeC(0xe0+j);
|
||||
foundShort=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundShort) {
|
||||
chanStream[i]->writeC(next);
|
||||
chanStream[i]->writeC(delay);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
chanStream[i]->writeC(next);
|
||||
break;
|
||||
}
|
||||
} catch (EndOfFileException& e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
oldStream->finish();
|
||||
delete oldStream;
|
||||
}
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
chanStreamOff[i]=w->tell();
|
||||
logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size());
|
||||
w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size());
|
||||
chanStream[i]->finish();
|
||||
delete chanStream[i];
|
||||
}
|
||||
|
||||
w->seek(8,SEEK_SET);
|
||||
for (int i=0; i<chans; i++) {
|
||||
w->writeI(chanStreamOff[i]);
|
||||
}
|
||||
|
||||
logD("delay popularity:");
|
||||
for (int i=0; i<16; i++) {
|
||||
w->writeC(sortedDelay[i]);
|
||||
if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]);
|
||||
}
|
||||
|
||||
logD("command popularity:");
|
||||
for (int i=0; i<16; i++) {
|
||||
w->writeC(sortedCmd[i]);
|
||||
if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]);
|
||||
}
|
||||
} else {
|
||||
if (!playing) {
|
||||
w->writeText(">> END\n");
|
||||
} else {
|
||||
w->writeText(">> LOOP 0\n");
|
||||
}
|
||||
}
|
||||
|
||||
remainingLoops=-1;
|
||||
playing=false;
|
||||
freelance=false;
|
||||
extValuePresent=false;
|
||||
BUSY_END;
|
||||
|
||||
return w;
|
||||
}
|
|
@ -411,6 +411,13 @@ class DivDispatch {
|
|||
*/
|
||||
virtual DivMacroInt* getChanMacroInt(int chan);
|
||||
|
||||
/**
|
||||
* get the stereo panning of a channel.
|
||||
* @param chan the channel.
|
||||
* @return a 16-bit number. left in top 8 bits and right in bottom 8 bits.
|
||||
*/
|
||||
virtual unsigned short getPan(int chan);
|
||||
|
||||
/**
|
||||
* get currently playing sample (and its position).
|
||||
* @param chan the channel.
|
||||
|
|
|
@ -219,7 +219,7 @@ void DivDispatchContainer::clear() {
|
|||
}
|
||||
}
|
||||
|
||||
void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, double gotRate, const DivConfig& flags) {
|
||||
void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, double gotRate, const DivConfig& flags, bool isRender) {
|
||||
// quit if we already initialized
|
||||
if (dispatch!=NULL) return;
|
||||
|
||||
|
@ -231,33 +231,57 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_YM2612:
|
||||
dispatch=new DivPlatformGenesis;
|
||||
((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
if (isRender) {
|
||||
((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612CoreRender",0));
|
||||
} else {
|
||||
((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
}
|
||||
((DivPlatformGenesis*)dispatch)->setSoftPCM(false);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2612_EXT:
|
||||
dispatch=new DivPlatformGenesisExt;
|
||||
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
if (isRender) {
|
||||
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612CoreRender",0));
|
||||
} else {
|
||||
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
}
|
||||
((DivPlatformGenesisExt*)dispatch)->setSoftPCM(false);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2612_CSM:
|
||||
dispatch=new DivPlatformGenesisExt;
|
||||
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
if (isRender) {
|
||||
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612CoreRender",0));
|
||||
} else {
|
||||
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
}
|
||||
((DivPlatformGenesisExt*)dispatch)->setSoftPCM(false);
|
||||
((DivPlatformGenesisExt*)dispatch)->setCSMChannel(6);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2612_DUALPCM:
|
||||
dispatch=new DivPlatformGenesis;
|
||||
((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
if (isRender) {
|
||||
((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612CoreRender",0));
|
||||
} else {
|
||||
((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
}
|
||||
((DivPlatformGenesis*)dispatch)->setSoftPCM(true);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
|
||||
dispatch=new DivPlatformGenesisExt;
|
||||
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
if (isRender) {
|
||||
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612CoreRender",0));
|
||||
} else {
|
||||
((DivPlatformGenesisExt*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0));
|
||||
}
|
||||
((DivPlatformGenesisExt*)dispatch)->setSoftPCM(true);
|
||||
break;
|
||||
case DIV_SYSTEM_SMS:
|
||||
dispatch=new DivPlatformSMS;
|
||||
((DivPlatformSMS*)dispatch)->setNuked(eng->getConfInt("snCore",0));
|
||||
if (isRender) {
|
||||
((DivPlatformSMS*)dispatch)->setNuked(eng->getConfInt("snCoreRender",0));
|
||||
} else {
|
||||
((DivPlatformSMS*)dispatch)->setNuked(eng->getConfInt("snCore",0));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_GB:
|
||||
dispatch=new DivPlatformGB;
|
||||
|
@ -267,39 +291,71 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
dispatch=new DivPlatformNES;
|
||||
((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCore",0)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCoreRender",0)==1);
|
||||
} else {
|
||||
((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCore",0)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_C64_6581:
|
||||
dispatch=new DivPlatformC64;
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0));
|
||||
if (isRender) {
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64CoreRender",1));
|
||||
} else {
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0));
|
||||
}
|
||||
((DivPlatformC64*)dispatch)->setChipModel(true);
|
||||
break;
|
||||
case DIV_SYSTEM_C64_8580:
|
||||
dispatch=new DivPlatformC64;
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0));
|
||||
if (isRender) {
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64CoreRender",1));
|
||||
} else {
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0));
|
||||
}
|
||||
((DivPlatformC64*)dispatch)->setChipModel(false);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2151:
|
||||
dispatch=new DivPlatformArcade;
|
||||
((DivPlatformArcade*)dispatch)->setYMFM(eng->getConfInt("arcadeCore",0)==0);
|
||||
if (isRender) {
|
||||
((DivPlatformArcade*)dispatch)->setYMFM(eng->getConfInt("arcadeCoreRender",1)==0);
|
||||
} else {
|
||||
((DivPlatformArcade*)dispatch)->setYMFM(eng->getConfInt("arcadeCore",0)==0);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610:
|
||||
case DIV_SYSTEM_YM2610_FULL:
|
||||
dispatch=new DivPlatformYM2610;
|
||||
((DivPlatformYM2610*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformYM2610*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformYM2610*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610_EXT:
|
||||
case DIV_SYSTEM_YM2610_FULL_EXT:
|
||||
dispatch=new DivPlatformYM2610Ext;
|
||||
((DivPlatformYM2610Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformYM2610Ext*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformYM2610Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610B:
|
||||
dispatch=new DivPlatformYM2610B;
|
||||
((DivPlatformYM2610B*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformYM2610B*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformYM2610B*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610B_EXT:
|
||||
dispatch=new DivPlatformYM2610BExt;
|
||||
((DivPlatformYM2610BExt*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformYM2610BExt*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformYM2610BExt*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_AMIGA:
|
||||
dispatch=new DivPlatformAmiga;
|
||||
|
@ -312,26 +368,46 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_FDS:
|
||||
dispatch=new DivPlatformFDS;
|
||||
((DivPlatformFDS*)dispatch)->setNSFPlay(eng->getConfInt("fdsCore",0)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformFDS*)dispatch)->setNSFPlay(eng->getConfInt("fdsCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformFDS*)dispatch)->setNSFPlay(eng->getConfInt("fdsCore",0)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_TIA:
|
||||
dispatch=new DivPlatformTIA;
|
||||
break;
|
||||
case DIV_SYSTEM_YM2203:
|
||||
dispatch=new DivPlatformYM2203;
|
||||
((DivPlatformYM2203*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformYM2203*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformYM2203*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2203_EXT:
|
||||
dispatch=new DivPlatformYM2203Ext;
|
||||
((DivPlatformYM2203Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformYM2203Ext*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformYM2203Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2608:
|
||||
dispatch=new DivPlatformYM2608;
|
||||
((DivPlatformYM2608*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformYM2608*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformYM2608*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2608_EXT:
|
||||
dispatch=new DivPlatformYM2608Ext;
|
||||
((DivPlatformYM2608Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformYM2608Ext*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformYM2608Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
|
@ -396,7 +472,11 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_POKEY:
|
||||
dispatch=new DivPlatformPOKEY;
|
||||
((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCore",1)==1);
|
||||
if (isRender) {
|
||||
((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCoreRender",1)==1);
|
||||
} else {
|
||||
((DivPlatformPOKEY*)dispatch)->setAltASAP(eng->getConfInt("pokeyCore",1)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
dispatch=new DivPlatformQSound;
|
||||
|
@ -507,6 +587,11 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_C140:
|
||||
dispatch=new DivPlatformC140;
|
||||
((DivPlatformC140*)dispatch)->set219(false);
|
||||
break;
|
||||
case DIV_SYSTEM_C219:
|
||||
dispatch=new DivPlatformC140;
|
||||
((DivPlatformC140*)dispatch)->set219(true);
|
||||
break;
|
||||
case DIV_SYSTEM_PCM_DAC:
|
||||
dispatch=new DivPlatformPCMDAC;
|
||||
|
|
|
@ -56,8 +56,8 @@
|
|||
|
||||
#define DIV_UNSTABLE
|
||||
|
||||
#define DIV_VERSION "dev165"
|
||||
#define DIV_ENGINE_VERSION 165
|
||||
#define DIV_VERSION "dev169"
|
||||
#define DIV_ENGINE_VERSION 169
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
#define DIV_VERSION_FC 0xff02
|
||||
|
@ -201,7 +201,7 @@ struct DivDispatchContainer {
|
|||
void flush(size_t count);
|
||||
void fillBuf(size_t runtotal, size_t offset, size_t size);
|
||||
void clear();
|
||||
void init(DivSystem sys, DivEngine* eng, int chanCount, double gotRate, const DivConfig& flags);
|
||||
void init(DivSystem sys, DivEngine* eng, int chanCount, double gotRate, const DivConfig& flags, bool isRender=false);
|
||||
void quit();
|
||||
DivDispatchContainer():
|
||||
dispatch(NULL),
|
||||
|
@ -497,6 +497,7 @@ class DivEngine {
|
|||
void playSub(bool preserveDrift, int goalRow=0);
|
||||
void runMidiClock(int totalCycles=1);
|
||||
void runMidiTime(int totalCycles=1);
|
||||
bool shallSwitchCores();
|
||||
|
||||
void testFunction();
|
||||
|
||||
|
@ -614,6 +615,8 @@ class DivEngine {
|
|||
void waitAudioFile();
|
||||
// stop audio file export
|
||||
bool haltAudioFile();
|
||||
// return back to playback cores if necessary
|
||||
void finishAudioFile();
|
||||
// notify instrument parameter change
|
||||
void notifyInsChange(int ins);
|
||||
// notify wavetable change
|
||||
|
@ -973,6 +976,9 @@ class DivEngine {
|
|||
// get macro interpreter
|
||||
DivMacroInt* getMacroInt(int chan);
|
||||
|
||||
// get channel panning
|
||||
unsigned short getChanPan(int chan);
|
||||
|
||||
// get sample position
|
||||
DivSamplePos getSamplePos(int chan);
|
||||
|
||||
|
@ -1132,7 +1138,7 @@ class DivEngine {
|
|||
TAAudioDesc& getAudioDescGot();
|
||||
|
||||
// init dispatch
|
||||
void initDispatch();
|
||||
void initDispatch(bool isRender=false);
|
||||
|
||||
// quit dispatch
|
||||
void quitDispatch();
|
||||
|
|
|
@ -266,6 +266,7 @@ std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
|
|||
}
|
||||
|
||||
// finish
|
||||
ret.reserve(5);
|
||||
ret.push_back(DivROMExportOutput("sbook.bin",sbook));
|
||||
ret.push_back(DivROMExportOutput("wbook.bin",wbook));
|
||||
ret.push_back(DivROMExportOutput("sample.bin",sample));
|
||||
|
|
|
@ -183,6 +183,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
ds.brokenPortaArp=false;
|
||||
ds.snNoLowPeriods=true;
|
||||
ds.disableSampleMacro=true;
|
||||
ds.preNoteNoEffect=true;
|
||||
ds.delayBehavior=0;
|
||||
ds.jumpTreatment=2;
|
||||
|
||||
|
@ -341,6 +342,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
ds.insLen=16;
|
||||
}
|
||||
logI("reading instruments (%d)...",ds.insLen);
|
||||
ds.ins.reserve(ds.insLen);
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
unsigned char mode=0;
|
||||
|
@ -668,6 +670,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
if (ds.version>0x0b) {
|
||||
ds.waveLen=(unsigned char)reader.readC();
|
||||
logI("reading wavetables (%d)...",ds.waveLen);
|
||||
ds.wave.reserve(ds.waveLen);
|
||||
for (int i=0; i<ds.waveLen; i++) {
|
||||
DivWavetable* wave=new DivWavetable;
|
||||
wave->len=(unsigned char)reader.readI();
|
||||
|
@ -837,6 +840,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
// it appears this byte stored the YMU759 sample rate
|
||||
ymuSampleRate=reader.readC();
|
||||
}
|
||||
ds.sample.reserve(ds.sampleLen);
|
||||
for (int i=0; i<ds.sampleLen; i++) {
|
||||
DivSample* sample=new DivSample;
|
||||
int length=reader.readI();
|
||||
|
@ -1844,6 +1848,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
if (ds.version<155) {
|
||||
ds.brokenFMOff=true;
|
||||
}
|
||||
if (ds.version<168) {
|
||||
ds.preNoteNoEffect=true;
|
||||
}
|
||||
ds.isDMF=false;
|
||||
|
||||
reader.readS(); // reserved
|
||||
|
@ -2123,6 +2130,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
for (int i=0; i<ds.sampleLen; i++) {
|
||||
samplePtr[i]=reader.readI();
|
||||
}
|
||||
patPtr.reserve(numberOfPats);
|
||||
for (int i=0; i<numberOfPats; i++) patPtr.push_back(reader.readI());
|
||||
|
||||
logD("reading orders (%d)...",subSong->ordersLen);
|
||||
|
@ -2341,6 +2349,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
|
||||
// patchbay
|
||||
unsigned int conns=reader.readI();
|
||||
ds.patchbay.reserve(conns);
|
||||
for (unsigned int i=0; i<conns; i++) {
|
||||
ds.patchbay.push_back((unsigned int)reader.readI());
|
||||
}
|
||||
|
@ -2355,7 +2364,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
for (int i=0; i<6; i++) {
|
||||
if (ds.version>=168) {
|
||||
ds.preNoteNoEffect=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
for (int i=0; i<5; i++) {
|
||||
reader.readC();
|
||||
}
|
||||
}
|
||||
|
@ -2368,6 +2382,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
|
||||
// grooves
|
||||
unsigned char grooveCount=reader.readC();
|
||||
ds.grooves.reserve(grooveCount);
|
||||
for (int i=0; i<grooveCount; i++) {
|
||||
DivGroovePattern gp;
|
||||
gp.len=reader.readC();
|
||||
|
@ -2468,6 +2483,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
|
||||
// read subsongs
|
||||
if (ds.version>=95) {
|
||||
ds.subsong.reserve(numberOfSubSongs);
|
||||
for (int i=0; i<numberOfSubSongs; i++) {
|
||||
ds.subsong.push_back(new DivSubSong);
|
||||
if (!reader.seek(subSongPtr[i],SEEK_SET)) {
|
||||
|
@ -2549,6 +2565,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
}
|
||||
|
||||
// read instruments
|
||||
ds.ins.reserve(ds.insLen);
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
logD("reading instrument %d at %x...",i,insPtr[i]);
|
||||
|
@ -2573,6 +2590,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
}
|
||||
|
||||
// read wavetables
|
||||
ds.wave.reserve(ds.waveLen);
|
||||
for (int i=0; i<ds.waveLen; i++) {
|
||||
DivWavetable* wave=new DivWavetable;
|
||||
logD("reading wavetable %d at %x...",i,wavePtr[i]);
|
||||
|
@ -2597,6 +2615,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
}
|
||||
|
||||
// read samples
|
||||
ds.sample.reserve(ds.sampleLen);
|
||||
for (int i=0; i<ds.sampleLen; i++) {
|
||||
DivSample* sample=new DivSample;
|
||||
|
||||
|
@ -3060,6 +3079,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
|
||||
// samples
|
||||
logD("reading samples... (%d)",insCount);
|
||||
ds.sample.reserve(insCount);
|
||||
for (int i=0; i<insCount; i++) {
|
||||
DivSample* sample=new DivSample;
|
||||
sample->depth=DIV_SAMPLE_DEPTH_8BIT;
|
||||
|
@ -3364,6 +3384,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
}
|
||||
|
||||
// instrument creation
|
||||
ds.ins.reserve(insCount);
|
||||
for(int i=0; i<insCount; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
ins->type=DIV_INS_AMIGA;
|
||||
|
@ -3642,6 +3663,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
|
|||
}
|
||||
|
||||
// load instruments/samples
|
||||
ds.ins.reserve(ds.insLen);
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
if (!reader.seek(0x4c+insPtr[i]*16,SEEK_SET)) {
|
||||
|
@ -3868,6 +3890,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
// sequences
|
||||
seqLen/=13;
|
||||
logD("reading sequences... (%d)",seqLen);
|
||||
seq.reserve(seqLen);
|
||||
for (unsigned int i=0; i<seqLen; i++) {
|
||||
FCSequence s;
|
||||
for (int j=0; j<4; j++) {
|
||||
|
@ -3897,6 +3920,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
}
|
||||
patLen/=64;
|
||||
logD("reading patterns... (%d)",patLen);
|
||||
pat.reserve(patLen);
|
||||
for (unsigned int i=0; i<patLen; i++) {
|
||||
FCPattern p;
|
||||
logV("- pattern %d",i);
|
||||
|
@ -3917,6 +3941,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
}
|
||||
freqMacroLen/=64;
|
||||
logD("reading freq sequences... (%d)",freqMacroLen);
|
||||
freqMacros.reserve(freqMacroLen);
|
||||
for (unsigned int i=0; i<freqMacroLen; i++) {
|
||||
FCMacro m;
|
||||
reader.read(m.val,64);
|
||||
|
@ -3932,6 +3957,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
}
|
||||
volMacroLen/=64;
|
||||
logD("reading volume sequences... (%d)",volMacroLen);
|
||||
volMacros.reserve(volMacroLen);
|
||||
for (unsigned int i=0; i<volMacroLen; i++) {
|
||||
FCMacro m;
|
||||
reader.read(m.val,64);
|
||||
|
@ -3946,6 +3972,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
return false;
|
||||
}
|
||||
logD("reading samples...");
|
||||
ds.sample.reserve(10);
|
||||
for (int i=0; i<10; i++) {
|
||||
DivSample* s=new DivSample;
|
||||
s->depth=DIV_SAMPLE_DEPTH_8BIT;
|
||||
|
@ -3972,6 +3999,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
return false;
|
||||
}
|
||||
logD("reading wavetables...");
|
||||
ds.wave.reserve(80);
|
||||
for (int i=0; i<80; i++) {
|
||||
DivWavetable* w=new DivWavetable;
|
||||
w->min=0;
|
||||
|
@ -4000,6 +4028,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
}
|
||||
} else {
|
||||
// generate preset waves
|
||||
ds.wave.reserve(48);
|
||||
for (int i=0; i<48; i++) {
|
||||
DivWavetable* w=new DivWavetable;
|
||||
generateFCPresetWave(i,w);
|
||||
|
@ -4147,6 +4176,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
|
||||
// volume sequence
|
||||
ins->std.volMacro.len=0;
|
||||
ds.ins.reserve(64 - 5);
|
||||
for (int j=5; j<64; j++) {
|
||||
loopMap[j]=ins->std.volMacro.len;
|
||||
if (m.val[j]==0xe1) { // end
|
||||
|
@ -4544,6 +4574,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
CHECK_BLOCK_VERSION(4);
|
||||
unsigned char totalSongs=reader.readC();
|
||||
logV("%d songs:",totalSongs+1);
|
||||
ds.subsong.reserve(totalSongs);
|
||||
for (int i=0; i<=totalSongs; i++) {
|
||||
String subSongName=reader.readString();
|
||||
ds.subsong.push_back(new DivSubSong);
|
||||
|
@ -5078,12 +5109,14 @@ DivDataErrors DivEngine::readAssetDirData(SafeReader& reader, std::vector<DivAss
|
|||
|
||||
unsigned int numDirs=reader.readI();
|
||||
|
||||
dir.reserve(numDirs);
|
||||
for (unsigned int i=0; i<numDirs; i++) {
|
||||
DivAssetDir d;
|
||||
|
||||
d.name=reader.readString();
|
||||
unsigned short numEntries=reader.readS();
|
||||
|
||||
d.entries.reserve(numEntries);
|
||||
for (unsigned short j=0; j<numEntries; j++) {
|
||||
d.entries.push_back(((unsigned char)reader.readC()));
|
||||
}
|
||||
|
@ -5383,7 +5416,9 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
|
|||
|
||||
// even more compat flags
|
||||
w->writeC(song.brokenPortaLegato);
|
||||
for (int i=0; i<7; i++) {
|
||||
w->writeC(song.brokenFMOff);
|
||||
w->writeC(song.preNoteNoEffect);
|
||||
for (int i=0; i<5; i++) {
|
||||
w->writeC(0);
|
||||
}
|
||||
|
||||
|
@ -5414,6 +5449,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
|
|||
w->seek(0,SEEK_END);
|
||||
|
||||
/// SUBSONGS
|
||||
subSongPtr.reserve(song.subsong.size() - 1);
|
||||
for (subSongIndex=1; subSongIndex<song.subsong.size(); subSongIndex++) {
|
||||
subSong=song.subsong[subSongIndex];
|
||||
subSongPtr.push_back(w->tell());
|
||||
|
@ -5475,6 +5511,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
|
|||
}
|
||||
|
||||
/// CHIP FLAGS
|
||||
sysFlagsPtr.reserve(song.systemLen);
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
String data=song.systemFlags[i].toString();
|
||||
if (data.empty()) {
|
||||
|
@ -5504,6 +5541,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
|
|||
putAssetDirData(w,song.sampleDir);
|
||||
|
||||
/// INSTRUMENT
|
||||
insPtr.reserve(song.insLen);
|
||||
for (int i=0; i<song.insLen; i++) {
|
||||
DivInstrument* ins=song.ins[i];
|
||||
insPtr.push_back(w->tell());
|
||||
|
@ -5511,6 +5549,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
|
|||
}
|
||||
|
||||
/// WAVETABLE
|
||||
wavePtr.reserve(song.waveLen);
|
||||
for (int i=0; i<song.waveLen; i++) {
|
||||
DivWavetable* wave=song.wave[i];
|
||||
wavePtr.push_back(w->tell());
|
||||
|
@ -5518,6 +5557,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
|
|||
}
|
||||
|
||||
/// SAMPLE
|
||||
samplePtr.reserve(song.sampleLen);
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* sample=song.sample[i];
|
||||
samplePtr.push_back(w->tell());
|
||||
|
@ -5525,6 +5565,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
|
|||
}
|
||||
|
||||
/// PATTERN
|
||||
patPtr.reserve(patsToWrite.size());
|
||||
for (PatToWrite& i: patsToWrite) {
|
||||
DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false);
|
||||
patPtr.push_back(w->tell());
|
||||
|
|
511
src/engine/fileOpsSample.cpp
Normal file
|
@ -0,0 +1,511 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
#include "../fileutils.h"
|
||||
#ifdef HAVE_SNDFILE
|
||||
#include "sfWrapper.h"
|
||||
#endif
|
||||
|
||||
DivSample* DivEngine::sampleFromFile(const char* path) {
|
||||
if (song.sample.size()>=256) {
|
||||
lastError="too many samples!";
|
||||
return NULL;
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
warnings="";
|
||||
|
||||
const char* pathRedux=strrchr(path,DIR_SEPARATOR);
|
||||
if (pathRedux==NULL) {
|
||||
pathRedux=path;
|
||||
} else {
|
||||
pathRedux++;
|
||||
}
|
||||
String stripPath;
|
||||
const char* pathReduxEnd=strrchr(pathRedux,'.');
|
||||
if (pathReduxEnd==NULL) {
|
||||
stripPath=pathRedux;
|
||||
} else {
|
||||
for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) {
|
||||
stripPath+=*i;
|
||||
}
|
||||
}
|
||||
|
||||
const char* ext=strrchr(path,'.');
|
||||
if (ext!=NULL) {
|
||||
String extS;
|
||||
for (; *ext; ext++) {
|
||||
char i=*ext;
|
||||
if (i>='A' && i<='Z') {
|
||||
i+='a'-'A';
|
||||
}
|
||||
extS+=i;
|
||||
}
|
||||
if (extS==".dmc" || extS==".brr") { // read as .dmc or .brr
|
||||
size_t len=0;
|
||||
DivSample* sample=new DivSample;
|
||||
sample->name=stripPath;
|
||||
|
||||
FILE* f=ps_fopen(path,"rb");
|
||||
if (f==NULL) {
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_END)<0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
len=ftell(f);
|
||||
|
||||
if (len==0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="file is empty!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="file is invalid!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_SET)<0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (extS==".dmc") {
|
||||
sample->rate=33144;
|
||||
sample->centerRate=33144;
|
||||
sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM;
|
||||
sample->init(len*8);
|
||||
} else if (extS==".brr") {
|
||||
sample->rate=32000;
|
||||
sample->centerRate=32000;
|
||||
sample->depth=DIV_SAMPLE_DEPTH_BRR;
|
||||
sample->init(16*(len/9));
|
||||
} else {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="wait... is that right? no I don't think so...";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned char* dataBuf=sample->dataDPCM;
|
||||
if (extS==".brr") {
|
||||
dataBuf=sample->dataBRR;
|
||||
if ((len%9)==2) {
|
||||
// read loop position
|
||||
unsigned short loopPos=0;
|
||||
logD("BRR file has loop position");
|
||||
if (fread(&loopPos,1,2,f)!=2) {
|
||||
logW("could not read loop position! %s",strerror(errno));
|
||||
} else {
|
||||
#ifdef TA_BIG_ENDIAN
|
||||
loopPos=(loopPos>>8)|(loopPos<<8);
|
||||
#endif
|
||||
sample->loopStart=16*(loopPos/9);
|
||||
sample->loopEnd=sample->samples;
|
||||
sample->loop=true;
|
||||
sample->loopMode=DIV_SAMPLE_LOOP_FORWARD;
|
||||
}
|
||||
len-=2;
|
||||
if (len==0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="BRR sample is empty!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
} else if ((len%9)!=0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="possibly corrupt BRR sample!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (fread(dataBuf,1,len,f)==0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
BUSY_END;
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef HAVE_SNDFILE
|
||||
lastError="Furnace was not compiled with libsndfile!";
|
||||
return NULL;
|
||||
#else
|
||||
SF_INFO si;
|
||||
SFWrapper sfWrap;
|
||||
memset(&si,0,sizeof(SF_INFO));
|
||||
SNDFILE* f=sfWrap.doOpen(path,SFM_READ,&si);
|
||||
if (f==NULL) {
|
||||
BUSY_END;
|
||||
int err=sf_error(NULL);
|
||||
if (err==SF_ERR_SYSTEM) {
|
||||
lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno));
|
||||
} else {
|
||||
lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (si.frames>16777215) {
|
||||
lastError="this sample is too big! max sample size is 16777215.";
|
||||
sfWrap.doClose();
|
||||
BUSY_END;
|
||||
return NULL;
|
||||
}
|
||||
void* buf=NULL;
|
||||
sf_count_t sampleLen=sizeof(short);
|
||||
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
|
||||
logD("sample is 8-bit unsigned");
|
||||
buf=new unsigned char[si.channels*si.frames];
|
||||
sampleLen=sizeof(unsigned char);
|
||||
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
|
||||
logD("sample is 32-bit float");
|
||||
buf=new float[si.channels*si.frames];
|
||||
sampleLen=sizeof(float);
|
||||
} else {
|
||||
logD("sample is 16-bit signed");
|
||||
buf=new short[si.channels*si.frames];
|
||||
sampleLen=sizeof(short);
|
||||
}
|
||||
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8 || (si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
|
||||
if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) {
|
||||
logW("sample read size mismatch!");
|
||||
}
|
||||
} else {
|
||||
if (sf_read_short(f,(short*)buf,si.frames*si.channels)!=(si.frames*si.channels)) {
|
||||
logW("sample read size mismatch!");
|
||||
}
|
||||
}
|
||||
DivSample* sample=new DivSample;
|
||||
int sampleCount=(int)song.sample.size();
|
||||
sample->name=stripPath;
|
||||
|
||||
int index=0;
|
||||
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
|
||||
sample->depth=DIV_SAMPLE_DEPTH_8BIT;
|
||||
} else {
|
||||
sample->depth=DIV_SAMPLE_DEPTH_16BIT;
|
||||
}
|
||||
sample->init(si.frames);
|
||||
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
|
||||
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
|
||||
int averaged=0;
|
||||
for (int j=0; j<si.channels; j++) {
|
||||
averaged+=((int)((unsigned char*)buf)[i+j])-128;
|
||||
}
|
||||
averaged/=si.channels;
|
||||
sample->data8[index++]=averaged;
|
||||
}
|
||||
delete[] (unsigned char*)buf;
|
||||
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
|
||||
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
|
||||
float averaged=0.0f;
|
||||
for (int j=0; j<si.channels; j++) {
|
||||
averaged+=((float*)buf)[i+j];
|
||||
}
|
||||
averaged/=si.channels;
|
||||
averaged*=32767.0;
|
||||
if (averaged<-32768.0) averaged=-32768.0;
|
||||
if (averaged>32767.0) averaged=32767.0;
|
||||
sample->data16[index++]=averaged;
|
||||
}
|
||||
delete[] (float*)buf;
|
||||
} else {
|
||||
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
|
||||
int averaged=0;
|
||||
for (int j=0; j<si.channels; j++) {
|
||||
averaged+=((short*)buf)[i+j];
|
||||
}
|
||||
averaged/=si.channels;
|
||||
sample->data16[index++]=averaged;
|
||||
}
|
||||
delete[] (short*)buf;
|
||||
}
|
||||
|
||||
sample->rate=si.samplerate;
|
||||
if (sample->rate<4000) sample->rate=4000;
|
||||
if (sample->rate>96000) sample->rate=96000;
|
||||
sample->centerRate=si.samplerate;
|
||||
|
||||
SF_INSTRUMENT inst;
|
||||
if (sf_command(f, SFC_GET_INSTRUMENT, &inst, sizeof(inst)) == SF_TRUE)
|
||||
{
|
||||
// There's no documentation on libsndfile detune range, but the code
|
||||
// implies -50..50. Yet when loading a file you can get a >50 value.
|
||||
if(inst.detune > 50)
|
||||
inst.detune = inst.detune - 100;
|
||||
short pitch = ((0x3c-inst.basenote)*100) + inst.detune;
|
||||
sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0));
|
||||
if(inst.loop_count && inst.loops[0].mode >= SF_LOOP_FORWARD)
|
||||
{
|
||||
sample->loop=true;
|
||||
sample->loopMode=(DivSampleLoopMode)(inst.loops[0].mode-SF_LOOP_FORWARD);
|
||||
sample->loopStart=inst.loops[0].start;
|
||||
sample->loopEnd=inst.loops[0].end;
|
||||
if(inst.loops[0].end < (unsigned int)sampleCount)
|
||||
sampleCount=inst.loops[0].end;
|
||||
}
|
||||
else
|
||||
sample->loop=false;
|
||||
}
|
||||
|
||||
if (sample->centerRate<4000) sample->centerRate=4000;
|
||||
if (sample->centerRate>64000) sample->centerRate=64000;
|
||||
sfWrap.doClose();
|
||||
BUSY_END;
|
||||
return sample;
|
||||
#endif
|
||||
}
|
||||
|
||||
DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles) {
|
||||
if (song.sample.size()>=256) {
|
||||
lastError="too many samples!";
|
||||
return NULL;
|
||||
}
|
||||
if (channels<1) {
|
||||
channels=1;
|
||||
}
|
||||
if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) {
|
||||
if (channels!=1) {
|
||||
channels=1;
|
||||
}
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
warnings="";
|
||||
|
||||
const char* pathRedux=strrchr(path,DIR_SEPARATOR);
|
||||
if (pathRedux==NULL) {
|
||||
pathRedux=path;
|
||||
} else {
|
||||
pathRedux++;
|
||||
}
|
||||
String stripPath;
|
||||
const char* pathReduxEnd=strrchr(pathRedux,'.');
|
||||
if (pathReduxEnd==NULL) {
|
||||
stripPath=pathRedux;
|
||||
} else {
|
||||
for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) {
|
||||
stripPath+=*i;
|
||||
}
|
||||
}
|
||||
|
||||
size_t len=0;
|
||||
size_t lenDivided=0;
|
||||
DivSample* sample=new DivSample;
|
||||
sample->name=stripPath;
|
||||
|
||||
FILE* f=ps_fopen(path,"rb");
|
||||
if (f==NULL) {
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_END)<0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
len=ftell(f);
|
||||
|
||||
if (len==0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="file is empty!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="file is invalid!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_SET)<0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lenDivided=len/channels;
|
||||
|
||||
unsigned int samples=lenDivided;
|
||||
switch (depth) {
|
||||
case DIV_SAMPLE_DEPTH_1BIT:
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
|
||||
samples=lenDivided*8;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_A:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B:
|
||||
case DIV_SAMPLE_DEPTH_VOX:
|
||||
samples=lenDivided*2;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT:
|
||||
case DIV_SAMPLE_DEPTH_MULAW:
|
||||
samples=lenDivided;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_BRR:
|
||||
samples=16*((lenDivided+8)/9);
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_16BIT:
|
||||
samples=(lenDivided+1)/2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (samples>16777215) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="this sample is too big! max sample size is 16777215.";
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sample->rate=32000;
|
||||
sample->centerRate=32000;
|
||||
sample->depth=depth;
|
||||
sample->init(samples);
|
||||
|
||||
unsigned char* buf=new unsigned char[len];
|
||||
if (fread(buf,1,len,f)==0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
|
||||
delete[] buf;
|
||||
delete sample;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
// import sample
|
||||
size_t pos=0;
|
||||
if (depth==DIV_SAMPLE_DEPTH_16BIT) {
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
int accum=0;
|
||||
for (int j=0; j<channels; j++) {
|
||||
if (pos+1>=len) break;
|
||||
if (bigEndian) {
|
||||
accum+=(short)(((short)((buf[pos]<<8)|buf[pos+1]))^(unsign?0x8000:0));
|
||||
} else {
|
||||
accum+=(short)(((short)(buf[pos]|(buf[pos+1]<<8)))^(unsign?0x8000:0));
|
||||
}
|
||||
pos+=2;
|
||||
}
|
||||
accum/=channels;
|
||||
sample->data16[i]=accum;
|
||||
}
|
||||
} else if (depth==DIV_SAMPLE_DEPTH_8BIT) {
|
||||
for (unsigned int i=0; i<samples; i++) {
|
||||
int accum=0;
|
||||
for (int j=0; j<channels; j++) {
|
||||
if (pos>=len) break;
|
||||
accum+=(signed char)(buf[pos++]^(unsign?0x80:0));
|
||||
}
|
||||
accum/=channels;
|
||||
sample->data8[i]=accum;
|
||||
}
|
||||
} else {
|
||||
memcpy(sample->getCurBuf(),buf,len);
|
||||
}
|
||||
delete[] buf;
|
||||
|
||||
if (swapNibbles) {
|
||||
unsigned char* b=(unsigned char*)sample->getCurBuf();
|
||||
switch (depth) {
|
||||
case DIV_SAMPLE_DEPTH_1BIT:
|
||||
case DIV_SAMPLE_DEPTH_1BIT_DPCM:
|
||||
// reverse bit order
|
||||
for (unsigned int i=0; i<sample->getCurBufLen(); i++) {
|
||||
b[i]=(
|
||||
((b[i]&128)?1:0)|
|
||||
((b[i]&64)?2:0)|
|
||||
((b[i]&32)?4:0)|
|
||||
((b[i]&16)?8:0)|
|
||||
((b[i]&8)?16:0)|
|
||||
((b[i]&4)?32:0)|
|
||||
((b[i]&2)?64:0)|
|
||||
((b[i]&1)?128:0)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_YMZ_ADPCM:
|
||||
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_A:
|
||||
case DIV_SAMPLE_DEPTH_ADPCM_B:
|
||||
case DIV_SAMPLE_DEPTH_VOX:
|
||||
// swap nibbles
|
||||
for (unsigned int i=0; i<sample->getCurBufLen(); i++) {
|
||||
b[i]=(b[i]<<4)|(b[i]>>4);
|
||||
}
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_MULAW:
|
||||
// Namco to G.711
|
||||
// Namco: smmmmxxx
|
||||
// G.711: sxxxmmmm (^0xff)
|
||||
for (unsigned int i=0; i<sample->getCurBufLen(); i++) {
|
||||
b[i]=(((b[i]&7)<<4)|(((b[i]>>3)&15)^((b[i]&0x80)?15:0))|(b[i]&0x80))^0xff;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BUSY_END;
|
||||
return sample;
|
||||
}
|
|
@ -967,6 +967,10 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song) {
|
|||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_C219:
|
||||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
|
||||
case DIV_INS_MAX:
|
||||
break;
|
||||
|
@ -2261,6 +2265,19 @@ void DivInstrument::readFeatureOx(SafeReader& reader, int op, short version) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// <167 TL macro compat
|
||||
if (macroCode==6 && version<167) {
|
||||
if (target->open&6) {
|
||||
for (int j=0; j<2; j++) {
|
||||
target->val[j]^=0x7f;
|
||||
}
|
||||
} else {
|
||||
for (int j=0; j<target->len; j++) {
|
||||
target->val[j]^=0x7f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
READ_FEAT_END;
|
||||
|
@ -3319,6 +3336,21 @@ DivDataErrors DivInstrument::readInsDataOld(SafeReader &reader, short version) {
|
|||
}
|
||||
}
|
||||
|
||||
// <167 TL macro compat
|
||||
if (version<167) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (std.opMacros[i].tlMacro.open&6) {
|
||||
for (int j=0; j<2; j++) {
|
||||
std.opMacros[i].tlMacro.val[j]^=0x7f;
|
||||
}
|
||||
} else {
|
||||
for (int j=0; j<std.opMacros[i].tlMacro.len; j++) {
|
||||
std.opMacros[i].tlMacro.val[j]^=0x7f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DIV_DATA_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@ enum DivInstrumentType: unsigned short {
|
|||
// DIV_INS_YMF292=51,
|
||||
DIV_INS_TED=52,
|
||||
DIV_INS_C140=53,
|
||||
DIV_INS_C219=54,
|
||||
DIV_INS_MAX,
|
||||
DIV_INS_NULL
|
||||
};
|
||||
|
|
|
@ -56,6 +56,7 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
|
|||
has=false;
|
||||
return;
|
||||
}
|
||||
if (released && type==1 && lastPos<3) delay=0;
|
||||
if (delay>0) {
|
||||
delay--;
|
||||
if (!linger) had=false;
|
||||
|
@ -401,15 +402,13 @@ void DivMacroInt::init(DivInstrument* which) {
|
|||
if (macroSource[i]!=NULL) {
|
||||
macroList[i]->prepare(*macroSource[i],e);
|
||||
// check ADSR mode
|
||||
if ((macroSource[i]->open&6)==4) {
|
||||
hasRelease=false;
|
||||
} else if ((macroSource[i]->open&6)==2) {
|
||||
if ((macroSource[i]->open&6)==2) {
|
||||
if (macroSource[i]->val[8]>0) {
|
||||
hasRelease=true;
|
||||
}
|
||||
} else if (macroSource[i]->rel<macroSource[i]->len) {
|
||||
hasRelease=true;
|
||||
} else {
|
||||
hasRelease=(macroSource[i]->rel<macroSource[i]->len);
|
||||
}
|
||||
} else {
|
||||
hasRelease=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,10 @@ void* DivDispatch::getChanState(int chan) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
unsigned short DivDispatch::getPan(int chan) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
DivMacroInt* DivDispatch::getChanMacroInt(int chan) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -299,7 +299,7 @@ void DivPlatformArcade::tick(bool sysTick) {
|
|||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
if (m.tl.had) {
|
||||
op.tl=127-m.tl.val;
|
||||
op.tl=m.tl.val;
|
||||
if (!op.enable) {
|
||||
rWrite(baseAddr+ADDR_TL,127);
|
||||
} else if (KVS(i,j)) {
|
||||
|
@ -857,6 +857,10 @@ DivMacroInt* DivPlatformArcade::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformArcade::getPan(int ch) {
|
||||
return (chan[ch].chVolL<<8)|(chan[ch].chVolR);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformArcade::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ class DivPlatformArcade: public DivPlatformOPM {
|
|||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
unsigned short getPan(int chan);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void setFlags(const DivConfig& flags);
|
||||
|
|
|
@ -45,15 +45,63 @@ const char* regCheatSheetC140[]={
|
|||
NULL
|
||||
};
|
||||
|
||||
const char* regCheatSheetC219[]={
|
||||
"CHx_RVol", "00+x*10",
|
||||
"CHx_LVol", "01+x*10",
|
||||
"CHx_FreqH", "02+x*10",
|
||||
"CHx_FreqL", "03+x*10",
|
||||
"CHx_Ctrl", "05+x*10",
|
||||
"CHx_StartH", "06+x*10",
|
||||
"CHx_StartL", "07+x*10",
|
||||
"CHx_EndH", "08+x*10",
|
||||
"CHx_EndL", "09+x*10",
|
||||
"CHx_LoopH", "0A+x*10",
|
||||
"CHx_LoopL", "0B+x*10",
|
||||
"BankA", "1F7",
|
||||
"BankB", "1F1",
|
||||
"BankC", "1F3",
|
||||
"BankD", "1F5",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformC140::getRegisterSheet() {
|
||||
return regCheatSheetC140;
|
||||
return is219?regCheatSheetC219:regCheatSheetC140;
|
||||
}
|
||||
|
||||
void DivPlatformC140::acquire(short** buf, size_t len) {
|
||||
void DivPlatformC140::acquire_219(short** buf, size_t len) {
|
||||
for (size_t h=0; h<len; h++) {
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
c140_write(&c140, w.addr,w.val);
|
||||
c219_write(&c219,w.addr,w.val);
|
||||
regPool[w.addr&0x1ff]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
c219_tick(&c219, 1);
|
||||
// scale as 16bit
|
||||
c219.lout >>= 10;
|
||||
c219.rout >>= 10;
|
||||
|
||||
if (c219.lout<-32768) c219.lout=-32768;
|
||||
if (c219.lout>32767) c219.lout=32767;
|
||||
|
||||
if (c219.rout<-32768) c219.rout=-32768;
|
||||
if (c219.rout>32767) c219.rout=32767;
|
||||
|
||||
buf[0][h]=c219.lout;
|
||||
buf[1][h]=c219.rout;
|
||||
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(c219.voice[i].lout+c219.voice[i].rout)>>10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformC140::acquire_140(short** buf, size_t len) {
|
||||
for (size_t h=0; h<len; h++) {
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
c140_write(&c140,w.addr,w.val);
|
||||
regPool[w.addr&0x1ff]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
@ -72,14 +120,22 @@ void DivPlatformC140::acquire(short** buf, size_t len) {
|
|||
buf[0][h]=c140.lout;
|
||||
buf[1][h]=c140.rout;
|
||||
|
||||
for (int i=0; i<24; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(c140.voice[i].lout+c140.voice[i].rout)>>10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformC140::acquire(short** buf, size_t len) {
|
||||
if (is219) {
|
||||
acquire_219(buf,len);
|
||||
} else {
|
||||
acquire_140(buf,len);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformC140::tick(bool sysTick) {
|
||||
for (int i=0; i<24; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=(chan[i].vol*MIN(chan[i].macroVolMul,chan[i].std.vol.val))/chan[i].macroVolMul;
|
||||
|
@ -144,26 +200,48 @@ void DivPlatformC140::tick(bool sysTick) {
|
|||
chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE));
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x10:0)|((s->depth==DIV_SAMPLE_DEPTH_MULAW)?0x08:0);
|
||||
if (is219) {
|
||||
ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x10:0)|((s->depth==DIV_SAMPLE_DEPTH_MULAW)?1:0)|(chan[i].invert?0x40:0)|(chan[i].surround?8:0)|(chan[i].noise?4:0);
|
||||
} else {
|
||||
ctrl|=(chan[i].active?0x80:0)|((s->isLoopable())?0x10:0)|((s->depth==DIV_SAMPLE_DEPTH_MULAW)?0x08:0);
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
unsigned int bank=0;
|
||||
unsigned int start=0;
|
||||
unsigned int loop=0;
|
||||
unsigned int end=0;
|
||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
bank=(sampleOff[chan[i].sample]>>16)&0xff;
|
||||
start=sampleOff[chan[i].sample]&0xffff;
|
||||
end=MIN(start+s->length8-1,65535);
|
||||
if (is219) {
|
||||
bank=(sampleOff[chan[i].sample]>>16)&3;
|
||||
start=sampleOff[chan[i].sample]&0xffff;
|
||||
end=MIN(start+(s->length8>>1)-1,65535);
|
||||
} else {
|
||||
bank=(sampleOff[chan[i].sample]>>16)&0xff;
|
||||
start=sampleOff[chan[i].sample]&0xffff;
|
||||
end=MIN(start+s->length8-1,65535);
|
||||
}
|
||||
}
|
||||
if (chan[i].audPos>0) {
|
||||
start=MIN(start+MIN(chan[i].audPos,s->length8),65535);
|
||||
start=MIN(start+(MIN(chan[i].audPos,s->length8)>>1),65535);
|
||||
}
|
||||
if (s->isLoopable()) {
|
||||
loop=MIN(start+s->loopStart,65535);
|
||||
end=MIN(start+s->loopEnd-1,65535);
|
||||
if (is219) {
|
||||
loop=MIN(start+(s->loopStart>>1),65535);
|
||||
end=MIN(start+(s->loopEnd>>1)-1,65535);
|
||||
} else {
|
||||
loop=MIN(start+s->loopStart,65535);
|
||||
end=MIN(start+s->loopEnd-1,65535);
|
||||
}
|
||||
}
|
||||
rWrite(0x05+(i<<4),0); // force keyoff first
|
||||
rWrite(0x04+(i<<4),bank);
|
||||
if (is219) {
|
||||
if (groupBank[i>>2]!=bank) {
|
||||
groupBank[i>>2]=bank;
|
||||
}
|
||||
rWrite(0x1f1+(((3+(i>>2))&3)<<1),groupBank[i>>2]);
|
||||
} else {
|
||||
rWrite(0x04+(i<<4),bank);
|
||||
}
|
||||
rWrite(0x06+(i<<4),(start>>8)&0xff);
|
||||
rWrite(0x07+(i<<4),start&0xff);
|
||||
rWrite(0x08+(i<<4),(end>>8)&0xff);
|
||||
|
@ -323,11 +401,15 @@ int DivPlatformC140::dispatch(DivCommand c) {
|
|||
|
||||
void DivPlatformC140::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
c140.voice[ch].muted=mute;
|
||||
if (is219) {
|
||||
c219.voice[ch].muted=mute;
|
||||
} else {
|
||||
c140.voice[ch].muted=mute;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformC140::forceIns() {
|
||||
for (int i=0; i<24; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].volChangedL=true;
|
||||
|
@ -344,6 +426,10 @@ DivMacroInt* DivPlatformC140::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformC140::getPan(int ch) {
|
||||
return (chan[ch].chPanL<<8)|(chan[ch].chPanR);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformC140::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
@ -351,12 +437,19 @@ DivDispatchOscBuffer* DivPlatformC140::getOscBuffer(int ch) {
|
|||
void DivPlatformC140::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
memset(regPool,0,512);
|
||||
c140_reset(&c140);
|
||||
for (int i=0; i<24; i++) {
|
||||
if (is219) {
|
||||
c219_reset(&c219);
|
||||
} else {
|
||||
c140_reset(&c140);
|
||||
}
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
chan[i]=DivPlatformC140::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
rWrite(0x05+(i<<4),0);
|
||||
}
|
||||
for (int i=0; i<4; i++) {
|
||||
groupBank[i]=0;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformC140::getOutputCount() {
|
||||
|
@ -364,7 +457,7 @@ int DivPlatformC140::getOutputCount() {
|
|||
}
|
||||
|
||||
void DivPlatformC140::notifyInsChange(int ins) {
|
||||
for (int i=0; i<24; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
|
@ -377,7 +470,7 @@ void DivPlatformC140::notifyWaveChange(int wave) {
|
|||
}
|
||||
|
||||
void DivPlatformC140::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<24; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
@ -407,7 +500,7 @@ const void* DivPlatformC140::getSampleMem(int index) {
|
|||
}
|
||||
|
||||
size_t DivPlatformC140::getSampleMemCapacity(int index) {
|
||||
return index == 0 ? 16777216 : 0;
|
||||
return index == 0 ? (is219?524288:16777216) : 0;
|
||||
}
|
||||
|
||||
size_t DivPlatformC140::getSampleMemUsage(int index) {
|
||||
|
@ -433,55 +526,93 @@ void DivPlatformC140::renderSamples(int sysID) {
|
|||
continue;
|
||||
}
|
||||
|
||||
unsigned int length=s->length16;
|
||||
// fit sample size to single bank size
|
||||
if (length>(131072)) {
|
||||
length=131072;
|
||||
}
|
||||
if ((memPos&0xfe0000)!=((memPos+length)&0xfe0000)) {
|
||||
memPos=((memPos+0x1ffff)&0xfe0000);
|
||||
}
|
||||
if (memPos>=(getSampleMemCapacity())) {
|
||||
logW("out of C140 memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
// why is C140 not G.711-compliant? this weird bit mangling had me puzzled for 3 hours...
|
||||
if (memPos+length>=(getSampleMemCapacity())) {
|
||||
if (s->depth==DIV_SAMPLE_DEPTH_MULAW) {
|
||||
for (unsigned int i=0; i<(getSampleMemCapacity())-memPos; i++) {
|
||||
unsigned char x=s->dataMuLaw[i]^0xff;
|
||||
if (x&0x80) x^=15;
|
||||
unsigned char c140Mu=(x&0x80)|((x&15)<<3)|((x&0x70)>>4);
|
||||
sampleMem[i+(memPos/sizeof(short))]=((c140Mu)<<8);
|
||||
}
|
||||
} else {
|
||||
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,(getSampleMemCapacity())-memPos);
|
||||
if (is219) { // C219 (8-bit)
|
||||
unsigned int length=s->length8;
|
||||
// fit sample size to single bank size
|
||||
if (length>131072) {
|
||||
length=131072;
|
||||
}
|
||||
if (length&1) length++;
|
||||
if ((memPos&0xfe0000)!=((memPos+length)&0xfe0000)) {
|
||||
memPos=((memPos+0x1ffff)&0xfe0000);
|
||||
}
|
||||
if (memPos>=(getSampleMemCapacity())) {
|
||||
logW("out of C219 memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
if (memPos+length>=(getSampleMemCapacity())) {
|
||||
length=getSampleMemCapacity()-memPos;
|
||||
logW("out of C219 memory for sample %d!",i);
|
||||
}
|
||||
logW("out of C140 memory for sample %d!",i);
|
||||
} else {
|
||||
if (s->depth==DIV_SAMPLE_DEPTH_MULAW) {
|
||||
for (unsigned int i=0; i<length; i++) {
|
||||
unsigned char x=s->dataMuLaw[i]^0xff;
|
||||
if (x&0x80) x^=15;
|
||||
unsigned char c140Mu=(x&0x80)|((x&15)<<3)|((x&0x70)>>4);
|
||||
sampleMem[i+(memPos/sizeof(short))]=((c140Mu)<<8);
|
||||
if (i>=s->lengthMuLaw) {
|
||||
sampleMem[i+memPos]=0;
|
||||
} else {
|
||||
unsigned char x=s->dataMuLaw[i]^0xff;
|
||||
sampleMem[i+memPos]=x;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,length);
|
||||
for (unsigned int i=0; i<length; i++) {
|
||||
if (i>=s->length8) {
|
||||
sampleMem[memPos+i]=0;
|
||||
} else {
|
||||
sampleMem[memPos+i]=s->data8[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
sampleOff[i]=memPos>>1;
|
||||
sampleLoaded[i]=true;
|
||||
memPos+=length;
|
||||
} else { // C140 (16-bit)
|
||||
unsigned int length=s->length16;
|
||||
// fit sample size to single bank size
|
||||
if (length>(131072)) {
|
||||
length=131072;
|
||||
}
|
||||
if ((memPos&0xfe0000)!=((memPos+length)&0xfe0000)) {
|
||||
memPos=((memPos+0x1ffff)&0xfe0000);
|
||||
}
|
||||
if (memPos>=(getSampleMemCapacity())) {
|
||||
logW("out of C140 memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
// why is C140 not G.711-compliant? this weird bit mangling had me puzzled for 3 hours...
|
||||
if (memPos+length>=(getSampleMemCapacity())) {
|
||||
length=getSampleMemCapacity()-memPos;
|
||||
logW("out of C140 memory for sample %d!",i);
|
||||
}
|
||||
if (s->depth==DIV_SAMPLE_DEPTH_MULAW) {
|
||||
for (unsigned int i=0; i<length; i+=2) {
|
||||
if ((i>>1)>=s->lengthMuLaw) break;
|
||||
unsigned char x=s->dataMuLaw[i>>1]^0xff;
|
||||
if (x&0x80) x^=15;
|
||||
unsigned char c140Mu=(x&0x80)|((x&15)<<3)|((x&0x70)>>4);
|
||||
sampleMem[i+memPos]=0;
|
||||
sampleMem[1+i+memPos]=c140Mu;
|
||||
}
|
||||
} else {
|
||||
memcpy(sampleMem+memPos,s->data16,length);
|
||||
}
|
||||
sampleOff[i]=memPos>>1;
|
||||
sampleLoaded[i]=true;
|
||||
memPos+=length;
|
||||
}
|
||||
sampleOff[i]=memPos>>1;
|
||||
sampleLoaded[i]=true;
|
||||
memPos+=length;
|
||||
}
|
||||
sampleMemLen=memPos+256;
|
||||
}
|
||||
|
||||
void DivPlatformC140::set219(bool is_219) {
|
||||
is219=is_219;
|
||||
totalChans=is219?16:24;
|
||||
}
|
||||
|
||||
void DivPlatformC140::setFlags(const DivConfig& flags) {
|
||||
chipClock=32000*256; // 8.192MHz and 12.288MHz input, verified from Assault Schematics
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/192;
|
||||
for (int i=0; i<24; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
@ -491,23 +622,28 @@ int DivPlatformC140::init(DivEngine* p, int channels, int sugRate, const DivConf
|
|||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
|
||||
for (int i=0; i<24; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
sampleMem=new short[getSampleMemCapacity()>>1];
|
||||
sampleMem=new unsigned char[getSampleMemCapacity()];
|
||||
sampleMemLen=0;
|
||||
c140_init(&c140);
|
||||
c140.sample_mem=sampleMem;
|
||||
if (is219) {
|
||||
c219_init(&c219);
|
||||
c219.sample_mem=(signed char*)sampleMem;
|
||||
} else {
|
||||
c140_init(&c140);
|
||||
c140.sample_mem=(short*)sampleMem;
|
||||
}
|
||||
setFlags(flags);
|
||||
reset();
|
||||
|
||||
return 24;
|
||||
return totalChans;
|
||||
}
|
||||
|
||||
void DivPlatformC140::quit() {
|
||||
delete[] sampleMem;
|
||||
for (int i=0; i<24; i++) {
|
||||
for (int i=0; i<totalChans; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,14 +21,14 @@
|
|||
#define _C140_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include "sound/c140.h"
|
||||
#include "sound/c140_c219.h"
|
||||
#include "../fixedQueue.h"
|
||||
|
||||
class DivPlatformC140: public DivDispatch {
|
||||
struct Channel: public SharedChannel<int> {
|
||||
unsigned int audPos;
|
||||
int sample, wave;
|
||||
bool setPos, volChangedL, volChangedR;
|
||||
bool setPos, invert, surround, noise, volChangedL, volChangedR;
|
||||
int chPanL, chPanR;
|
||||
int chVolL, chVolR;
|
||||
int macroVolMul;
|
||||
|
@ -39,6 +39,9 @@ class DivPlatformC140: public DivDispatch {
|
|||
sample(-1),
|
||||
wave(-1),
|
||||
setPos(false),
|
||||
invert(false),
|
||||
surround(false),
|
||||
noise(false),
|
||||
volChangedL(false),
|
||||
volChangedR(false),
|
||||
chPanL(255),
|
||||
|
@ -53,8 +56,11 @@ class DivPlatformC140: public DivDispatch {
|
|||
bool isMuted[24];
|
||||
unsigned int sampleOff[256];
|
||||
bool sampleLoaded[256];
|
||||
bool is219;
|
||||
int totalChans;
|
||||
unsigned char groupBank[4];
|
||||
|
||||
signed short* sampleMem;
|
||||
unsigned char* sampleMem;
|
||||
size_t sampleMemLen;
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
|
@ -65,15 +71,20 @@ class DivPlatformC140: public DivDispatch {
|
|||
};
|
||||
FixedQueue<QueuedWrite,2048> writes;
|
||||
struct c140_t c140;
|
||||
struct c219_t c219;
|
||||
unsigned char regPool[512];
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
void acquire_219(short** buf, size_t len);
|
||||
void acquire_140(short** buf, size_t len);
|
||||
|
||||
public:
|
||||
void acquire(short** buf, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
unsigned short getPan(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
@ -94,6 +105,7 @@ class DivPlatformC140: public DivDispatch {
|
|||
size_t getSampleMemUsage(int index = 0);
|
||||
bool isSampleLoaded(int index, int sample);
|
||||
void renderSamples(int chipID);
|
||||
void set219(bool is_219);
|
||||
void setFlags(const DivConfig& flags);
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
|
|
|
@ -167,7 +167,7 @@ void DivPlatformES5506::acquire(short** buf, size_t len) {
|
|||
buf[(o<<1)|1][h]=es5506.rout(o);
|
||||
}
|
||||
for (int i=chanMax; i>=0; i--) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(es5506.voice_lout(i)+es5506.voice_rout(i))>>6;
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(es5506.voice_lout(i)+es5506.voice_rout(i))>>5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1057,6 +1057,10 @@ DivMacroInt* DivPlatformES5506::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformES5506::getPan(int ch) {
|
||||
return ((chan[ch].lVol>>4)<<8)|(chan[ch].rVol>>4);
|
||||
}
|
||||
|
||||
void DivPlatformES5506::reset() {
|
||||
while (!hostIntf32.empty()) hostIntf32.pop();
|
||||
while (!hostIntf8.empty()) hostIntf8.pop();
|
||||
|
|
|
@ -295,6 +295,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
virtual int dispatch(DivCommand c) override;
|
||||
virtual void* getChanState(int chan) override;
|
||||
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
||||
virtual unsigned short getPan(int chan) override;
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
|
||||
virtual unsigned char* getRegisterPool() override;
|
||||
virtual int getRegisterPoolSize() override;
|
||||
|
|
|
@ -155,6 +155,7 @@ class DivPlatformOPN: public DivPlatformFMBase {
|
|||
unsigned int ayDiv;
|
||||
unsigned char csmChan;
|
||||
unsigned char lfoValue;
|
||||
unsigned char lastExtChPan;
|
||||
unsigned short ssgVol;
|
||||
unsigned short fmVol;
|
||||
bool extSys, useCombo, fbAllOps;
|
||||
|
@ -175,6 +176,7 @@ class DivPlatformOPN: public DivPlatformFMBase {
|
|||
ayDiv(a),
|
||||
csmChan(cc),
|
||||
lfoValue(0),
|
||||
lastExtChPan(3),
|
||||
ssgVol(128),
|
||||
fmVol(256),
|
||||
extSys(isExtSys),
|
||||
|
|
|
@ -458,7 +458,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].envVol=chan[c.chan].outVol;
|
||||
|
||||
if (!chan[c.chan].keyOn) chan[c.chan].killIt=true;
|
||||
if (!chan[c.chan].keyOn && chan[c.chan].active) chan[c.chan].killIt=true;
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
break;
|
||||
|
@ -578,6 +578,11 @@ DivMacroInt* DivPlatformGB::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformGB::getPan(int ch) {
|
||||
unsigned char p=lastPan&(0x11<<ch);
|
||||
return ((p&0xf0)?0x100:0)|((p&0x0f)?1:0);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformGB::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ class DivPlatformGB: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
unsigned short getPan(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
@ -473,7 +473,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
if (m.tl.had) {
|
||||
op.tl=127-m.tl.val;
|
||||
op.tl=m.tl.val;
|
||||
if (isMuted[i] || !op.enable) {
|
||||
rWrite(baseAddr+ADDR_TL,127);
|
||||
} else {
|
||||
|
@ -1274,6 +1274,11 @@ DivMacroInt* DivPlatformGenesis::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformGenesis::getPan(int ch) {
|
||||
if (ch>5) ch=5;
|
||||
return ((chan[ch].pan&2)<<7)|(chan[ch].pan&1);
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformGenesis::getSamplePos(int ch) {
|
||||
if (!chan[5].dacMode) return DivSamplePos();
|
||||
if (ch<5) return DivSamplePos();
|
||||
|
|
|
@ -106,6 +106,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
virtual unsigned short getPan(int chan);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
|
|
|
@ -159,6 +159,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
}
|
||||
}
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
lastExtChPan=opChan[ch].pan;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PITCH: {
|
||||
|
@ -443,13 +444,15 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
|
|||
DivPlatformGenesis::muteChannel(extChanOffs,IS_EXTCH_MUTED);
|
||||
|
||||
if (extMode) {
|
||||
int ordch=orderedOps[ch-2];
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
|
||||
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
|
||||
if (isOpMuted[ch-2] || !op.enable) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
for (int i=0; i<4; i++) {
|
||||
int ordch=orderedOps[i];
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
|
||||
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
|
||||
if (isOpMuted[i] || !op.enable) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
|
||||
}
|
||||
}
|
||||
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
|
@ -557,6 +560,17 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
rWrite(0x22,lfoValue);
|
||||
}
|
||||
|
||||
if (opChan[i].std.panL.had) {
|
||||
opChan[i].pan=opChan[i].std.panL.val&3;
|
||||
if (parent->song.sharedExtStat) {
|
||||
for (int j=0; j<4; j++) {
|
||||
if (i==j) continue;
|
||||
opChan[j].pan=opChan[i].pan;
|
||||
}
|
||||
}
|
||||
rWrite(chanOffs[extChanOffs]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[i].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
|
||||
}
|
||||
|
||||
// param macros
|
||||
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]];
|
||||
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]];
|
||||
|
@ -586,7 +600,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
|
||||
}
|
||||
if (m.tl.had) {
|
||||
op.tl=127-m.tl.val;
|
||||
op.tl=m.tl.val;
|
||||
if (isOpMuted[i]) {
|
||||
rWrite(baseAddr+ADDR_TL,127);
|
||||
} else {
|
||||
|
@ -743,7 +757,7 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
}
|
||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||
if (i==2) {
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(lastExtChPan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
} else {
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
}
|
||||
|
@ -770,6 +784,9 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
chan[csmChan].freqChanged=true;
|
||||
chan[csmChan].keyOn=true;
|
||||
}
|
||||
if (!extMode) {
|
||||
immWrite(0x27,0x00);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformGenesisExt::getChanState(int ch) {
|
||||
|
@ -784,6 +801,19 @@ DivMacroInt* DivPlatformGenesisExt::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformGenesisExt::getPan(int ch) {
|
||||
if (ch==csmChan) return 0;
|
||||
if (ch>=4+extChanOffs) return DivPlatformGenesis::getPan(ch-3);
|
||||
if (ch>=extChanOffs) {
|
||||
if (extMode) {
|
||||
return ((lastExtChPan&2)<<7)|(lastExtChPan&1);
|
||||
} else {
|
||||
return DivPlatformGenesis::getPan(extChanOffs);
|
||||
}
|
||||
}
|
||||
return DivPlatformGenesis::getPan(ch);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformGenesisExt::getOscBuffer(int ch) {
|
||||
if (ch>=6) return oscBuf[ch-3];
|
||||
if (ch<3) return oscBuf[ch];
|
||||
|
@ -800,6 +830,8 @@ void DivPlatformGenesisExt::reset() {
|
|||
opChan[i].outVol=127;
|
||||
}
|
||||
|
||||
lastExtChPan=3;
|
||||
|
||||
// channel 3 mode
|
||||
immWrite(0x27,0x40);
|
||||
extMode=true;
|
||||
|
|
|
@ -34,6 +34,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
unsigned short getPan(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
void reset();
|
||||
void forceIns();
|
||||
|
|
|
@ -78,15 +78,21 @@ void DivPlatformK007232::acquire(short** buf, size_t len) {
|
|||
const signed int rout[2]={(k007232.output(0)*((vol1>>4)&0xf)),(k007232.output(1)*((vol2>>4)&0xf))};
|
||||
buf[0][h]=(lout[0]+lout[1])<<4;
|
||||
buf[1][h]=(rout[0]+rout[1])<<4;
|
||||
for (int i=0; i<2; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(lout[i]+rout[i])<<3;
|
||||
if (++oscDivider>=8) {
|
||||
oscDivider=0;
|
||||
for (int i=0; i<2; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(lout[i]+rout[i])<<3;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const unsigned char vol=regPool[0xc];
|
||||
const signed int out[2]={(k007232.output(0)*(vol&0xf)),(k007232.output(1)*((vol>>4)&0xf))};
|
||||
buf[0][h]=(out[0]+out[1])<<4;
|
||||
for (int i=0; i<2; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=out[i]<<4;
|
||||
if (++oscDivider>=8) {
|
||||
oscDivider=0;
|
||||
for (int i=0; i<2; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=out[i]<<4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -424,6 +430,10 @@ DivMacroInt* DivPlatformK007232::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformK007232::getPan(int ch) {
|
||||
return ((chan[ch].panning&15)<<8)|((chan[ch].panning&0xf0)>>4);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformK007232::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
@ -480,7 +490,7 @@ void DivPlatformK007232::setFlags(const DivConfig& flags) {
|
|||
stereo=flags.getBool("stereo",false);
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i].volumeChanged=true;
|
||||
oscBuf[i]->rate=rate;
|
||||
oscBuf[i]->rate=rate/8;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -571,6 +581,7 @@ int DivPlatformK007232::init(DivEngine* p, int channels, int sugRate, const DivC
|
|||
}
|
||||
sampleMem=new unsigned char[getSampleMemCapacity()];
|
||||
sampleMemLen=0;
|
||||
oscDivider=0;
|
||||
setFlags(flags);
|
||||
reset();
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ class DivPlatformK007232: public DivDispatch, public k007232_intf {
|
|||
bool sampleLoaded[256];
|
||||
|
||||
int delay;
|
||||
unsigned char lastLoop, lastVolume;
|
||||
unsigned char lastLoop, lastVolume, oscDivider;
|
||||
bool stereo;
|
||||
|
||||
unsigned char* sampleMem;
|
||||
|
@ -85,6 +85,7 @@ class DivPlatformK007232: public DivDispatch, public k007232_intf {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
unsigned short getPan(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
@ -370,6 +370,10 @@ DivMacroInt* DivPlatformK053260::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformK053260::getPan(int ch) {
|
||||
return parent->convertPanLinearToSplit(chan[ch].panning,8,7);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformK053260::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ class DivPlatformK053260: public DivDispatch, public k053260_intf {
|
|||
virtual int dispatch(DivCommand c) override;
|
||||
virtual void* getChanState(int chan) override;
|
||||
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
||||
virtual unsigned short getPan(int chan) override;
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
|
||||
virtual unsigned char* getRegisterPool() override;
|
||||
virtual int getRegisterPoolSize() override;
|
||||
|
|
|
@ -238,7 +238,9 @@ void DivPlatformLynx::tick(bool sysTick) {
|
|||
chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
WRITE_FEEDBACK(i, chan[i].duty.feedback);
|
||||
if (!chan[i].pcm) {
|
||||
WRITE_FEEDBACK(i, chan[i].duty.feedback);
|
||||
}
|
||||
}
|
||||
WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7));
|
||||
WRITE_BACKUP( i, chan[i].fd.backup );
|
||||
|
@ -257,9 +259,21 @@ void DivPlatformLynx::tick(bool sysTick) {
|
|||
int DivPlatformLynx::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
bool prevPCM=chan[c.chan].pcm;
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY);
|
||||
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127;
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample);
|
||||
if (chan[c.chan].pcm!=prevPCM) {
|
||||
if (chan[c.chan].pcm) {
|
||||
WRITE_FEEDBACK(c.chan,0);
|
||||
WRITE_CONTROL(c.chan,0x18);
|
||||
WRITE_BACKUP(c.chan,0);
|
||||
} else {
|
||||
WRITE_FEEDBACK(c.chan,chan[c.chan].duty.feedback);
|
||||
WRITE_CONTROL(c.chan,(chan[c.chan].fd.clockDivider|0x18|chan[c.chan].duty.int_feedback7));
|
||||
WRITE_BACKUP(c.chan,chan[c.chan].fd.backup);
|
||||
}
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
if (chan[c.chan].pcm) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
|
@ -416,6 +430,10 @@ DivMacroInt* DivPlatformLynx::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformLynx::getPan(int ch) {
|
||||
return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15);
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformLynx::getSamplePos(int ch) {
|
||||
if (ch>=4) return DivSamplePos();
|
||||
if (!chan[ch].pcm) return DivSamplePos();
|
||||
|
|
|
@ -72,6 +72,7 @@ class DivPlatformLynx: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
unsigned short getPan(int chan);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
|
|