Merge branch 'master' of https://github.com/tildearrow/furnace into ymf278b

This commit is contained in:
cam900 2024-08-18 20:23:14 +09:00
commit 7fb7d32bd5
41 changed files with 1972 additions and 387 deletions

View file

@ -682,6 +682,14 @@ src/engine/fileOps/text.cpp
src/engine/fileOps/tfm.cpp src/engine/fileOps/tfm.cpp
src/engine/fileOps/xm.cpp src/engine/fileOps/xm.cpp
src/engine/fileOps/p.cpp
src/engine/fileOps/p86.cpp
src/engine/fileOps/pdx.cpp
src/engine/fileOps/ppc.cpp
src/engine/fileOps/pps.cpp
src/engine/fileOps/pvi.cpp
src/engine/fileOps/pzi.cpp
src/engine/blip_buf.c src/engine/blip_buf.c
src/engine/brrUtils.c src/engine/brrUtils.c
src/engine/safeReader.cpp src/engine/safeReader.cpp

Binary file not shown.

View file

@ -18,30 +18,31 @@ the default layout of Furnace is depicted below.
- [play/edit controls](play-edit-controls.md) - [play/edit controls](play-edit-controls.md)
- [instrument/wavetable/sample list](asset-list.md) - [instrument/wavetable/sample list](asset-list.md)
- [song information](song-info.md) - [song information](song-info.md)
- [effect list window](effect-list-window.md)
- [pattern view](../3-pattern/README.md) - [pattern view](../3-pattern/README.md)
- [effect list window](effect-list-window.md)
- [instrument editor](../4-instrument/README.md) - [instrument editor](../4-instrument/README.md)
- [wavetable editor](../5-wave/README.md) - [wavetable editor](../5-wave/README.md)
- [sample editor](../6-sample/README.md) - [sample editor](../6-sample/README.md)
## advanced topics ## advanced topics
- [mixer](../8-advanced/mixer.md)
- [grooves](../8-advanced/grooves.md)
- [channels](../8-advanced/channels.md)
- [pattern manager](../8-advanced/pat-manager.md)
- [chip manager](../8-advanced/chip-manager.md)
- [compatibility flags](../8-advanced/compat-flags.md)
- [song comments](../8-advanced/comments.md) - [song comments](../8-advanced/comments.md)
- [piano/input pad](../8-advanced/piano.md) - [channels](../8-advanced/channels.md)
- [chip manager](../8-advanced/chip-manager.md)
- [pattern manager](../8-advanced/pat-manager.md)
- [mixer](../8-advanced/mixer.md)
- [compatibility flags](../8-advanced/compat-flags.md)
- [oscilloscope](../8-advanced/osc.md) - [oscilloscope](../8-advanced/osc.md)
- [oscilloscope (per channel)](../8-advanced/chanosc.md) - [oscilloscope (per channel)](../8-advanced/chanosc.md)
- [oscilloscope (X-Y)](../8-advanced/xyosc.md)
- [clock](../8-advanced/clock.md) - [clock](../8-advanced/clock.md)
- [register view](../8-advanced/regview.md) - [grooves](../8-advanced/grooves.md)
- [log viewer](../8-advanced/log-viewer.md) - [log viewer](../8-advanced/log-viewer.md)
- [register view](../8-advanced/regview.md)
- [statistics](../8-advanced/stats.md) - [statistics](../8-advanced/stats.md)
- [memory composition](../8-advanced/memory-composition.md)
## other topics ## other topics
- [basic mode](basic-mode.md) - [piano/input pad](../8-advanced/piano.md)
- [settings](settings.md) - [settings](settings.md)

View file

@ -2,25 +2,27 @@
Furnace allows you to export your song in several formats. this section deals with describing the available export options. Furnace allows you to export your song in several formats. this section deals with describing the available export options.
## export audio ## audio
this option allows you to export your song in .wav format. I know I know, no .mp3 or .ogg export yet, but you can use a converter. this option allows you to export your song in .wav format. I know I know, no .mp3 or .ogg export yet, but you can use a converter.
there are two parameters: - **Export type**:
- **one file**: exports your song to one .wav file.
- **multiple files (one per chip)**: exports the output of each chip to .wav files.
- **multiple files (one per channel)**: exports the output of each channel to .wav files.
- useful for usage with a channel visualizer such as corrscope.
- **Bit depth**: default is 16-bit integer.
- **Sample rate**: affects the quality of the output file.
- default is 44100, "CD quality".
- lower sample rates lose fidelity as upper frequencies disappear.
- higher sample rates gain frequencies that can't be heard at the cost of file size and rendering time.
- **Channels in file**: default is 2 (stereo). Set to 1 for mono.
- **Loops**: sets the number of times the song will loop. - **Loops**: sets the number of times the song will loop.
- does not have effect if the song ends with `FFxx` effect. - does not have effect if the song ends with `FFxx` effect.
- **Fade out (seconds)**: sets the fade out time when the song is over. - **Fade out (seconds)**: sets the fade out time when the song is over.
- does not have effect if the song ends with `FFxx` effect. - does not have effect if the song ends with `FFxx` effect.
and three export choices: ## VGM
- **one file**: exports your song to one .wav file.
- **multiple files (one per chip)**: exports the output of each chip to .wav files.
- **multiple files (one per channel)**: exports the output of each channel to .wav files.
- useful for usage with a channel visualizer such as corrscope.
## export VGM
this option allows exporting to a VGM (Video Game Music) file. these can be played back with VGMPlay (for example). this option allows exporting to a VGM (Video Game Music) file. these can be played back with VGMPlay (for example).
@ -40,9 +42,6 @@ the following settings exist:
- **custom**: allows you to specify how many ticks to add. - **custom**: allows you to specify how many ticks to add.
- `0` is effectively none, disabling loop trail completely. - `0` is effectively none, disabling loop trail completely.
- this option will not appear if the loop modality isn't set to None as there wouldn't be a need to. - this option will not appear if the loop modality isn't set to None as there wouldn't be a need to.
- **chips to export**: select which chips are going to be exported.
- due to VGM format limitations, you can only select up to two of each chip type.
- some chips will not be available, either because VGM doesn't support these yet, or because you selected an old format version.
- **add pattern change hints**: this option adds a "hint" when a pattern change occurs. only useful if you're a developer. - **add pattern change hints**: this option adds a "hint" when a pattern change occurs. only useful if you're a developer.
- the format of the "hint" data block that gets written is: `67 66 FE ll ll ll ll 01 oo rr pp pp pp ...` - the format of the "hint" data block that gets written is: `67 66 FE ll ll ll ll 01 oo rr pp pp pp ...`
- `ll`: length, a 32-bit little-endian number - `ll`: length, a 32-bit little-endian number
@ -51,14 +50,11 @@ the following settings exist:
- `pp`: pattern index (one per channel) - `pp`: pattern index (one per channel)
- **direct stream mode**: this option allows DualPCM to work. don't use this for other chips. - **direct stream mode**: this option allows DualPCM to work. don't use this for other chips.
- may or may not play well with hardware VGM players. - may or may not play well with hardware VGM players.
- **chips to export**: select which chips are going to be exported.
- due to VGM format limitations, you can only select up to two of each chip type.
- some chips will not be available, either because VGM doesn't support these yet, or because you selected an old format version.
click on **click to export** to begin exporting. ## ZSM
## export text
this option allows you to export your song as a text file.
## export ZSM
ZSM (ZSound Music) is a format designed for the Commander X16 to allow hardware playback. ZSM (ZSound Music) is a format designed for the Commander X16 to allow hardware playback.
it may contain data for either YM2151 or VERA chips. it may contain data for either YM2151 or VERA chips.
@ -72,21 +68,21 @@ the following settings are available:
- **loop**: enables loop. if disabled, the song won't loop. - **loop**: enables loop. if disabled, the song won't loop.
- **optimize size**: removes unnecessary commands to reduce size. - **optimize size**: removes unnecessary commands to reduce size.
click on **Begin Export** to... you know. ## text
## export command stream this option allows you to export your song as a text file.
this option exports a binary file which contains a dump of the internal command stream produced when playing the song. ## command stream
it's not really useful, unless you're a developer and want to use a command stream dump for some reason (e.g. writing a hardware sound driver). this option exports a binary file in Furnace's own command stream format (FCS) which contains a dump of the internal command stream produced when playing the song.
- **export**: exports in Furnace's own command stream format (FCS). see `export-tech.md` in `papers/` for details. it's not really useful, unless you're a developer and want to use a command stream dump for some reason (e.g. writing a hardware sound driver). see `export-tech.md` in `papers/` for details.
## export DMF ## DMF
this option allows you to save your song as a .dmf which can be opened in DefleMask. this option allows you to save your song as a .dmf which can be opened in DefleMask.
the following systems are supported when saving as 1.0/legacy: the following systems are supported when saving as 1.0/legacy (0.12):
- Sega Genesis/Mega Drive (YM2612 + SN76489) - Sega Genesis/Mega Drive (YM2612 + SN76489)
- Sega Genesis/Mega Drive (YM2612 + SN76489, extended channel 3) - Sega Genesis/Mega Drive (YM2612 + SN76489, extended channel 3)
- Sega Master System - Sega Master System
@ -97,7 +93,7 @@ the following systems are supported when saving as 1.0/legacy:
- Arcade (YM2151 + SegaPCM 5-channel compatibility) - Arcade (YM2151 + SegaPCM 5-channel compatibility)
- Neo Geo CD (DefleMask 1.0+) - Neo Geo CD (DefleMask 1.0+)
the following systems are supported when saving as 1.1.3+: the following systems are also supported when saving as 1.1.3+:
- Sega Master System (with FM expansion) - Sega Master System (with FM expansion)
- NES + Konami VRC7 - NES + Konami VRC7
- Famicom Disk System - Famicom Disk System

View file

@ -39,7 +39,7 @@ the keys in the "Global hotkeys" section can be used in any window, although not
| Panic | `F12` | | Panic | `F12` |
| | | | | |
| **Window activation** | | | **Window activation** | |
| Find/Replace | Ctrl-F | | Find/Replace | `Ctrl-F` |
| Settings | — | | Settings | — |
| Song Information | — | | Song Information | — |
| Subsongs | — | | Subsongs | — |
@ -63,16 +63,23 @@ the keys in the "Global hotkeys" section can be used in any window, although not
| Piano | — | | Piano | — |
| Oscilloscope (master) | — | | Oscilloscope (master) | — |
| Oscilloscope (per-channel) | — | | Oscilloscope (per-channel) | — |
| Oscilloscope (X-Y) | — |
| Volume Meter | — | | Volume Meter | — |
| Clock | — | | Clock | — |
| Register View | — | | Register View | — |
| Log Viewer | — | | Log Viewer | — |
| Statistics | — | | Statistics | — |
| Memory Composition | — |
| Effect List | — | | Effect List | — |
| Debug Menu | `Ctrl-Shift-D` | | Debug Menu | `Ctrl-Shift-D` |
| Command Stream Player | — |
| About | — | | About | — |
| Collapse/expand current window | — | | Collapse/expand current window | — |
| Close current window | `Shift-Escape` | | Close current window | `Shift-Escape` |
| Command Palette | `Ctrl-P` |
| Recent files (Palette) | — |
| Insstruments (Palette) | — |
| Samples (Palette) | — |
| | | | | |
| **Note input** | | | **Note input** | |
| _see "note input" section after table_ | | | _see "note input" section after table_ | |
@ -102,8 +109,8 @@ the keys in the "Global hotkeys" section can be used in any window, although not
| Move cursor down by one (override Edit Step) | `Shift-End` | | Move cursor down by one (override Edit Step) | `Shift-End` |
| Move cursor to previous channel | — | | Move cursor to previous channel | — |
| Move cursor to next channel | — | | Move cursor to next channel | — |
| Move cursor to next channel (overflow) | — |
| Move cursor to previous channel (overflow) | — | | Move cursor to previous channel (overflow) | — |
| Move cursor to next channel (overflow) | — |
| Move cursor to beginning of pattern | `Home` | | Move cursor to beginning of pattern | `Home` |
| Move cursor to end of pattern | `End` | | Move cursor to end of pattern | `End` |
| Move cursor up (coarse) | `PageUp` | | Move cursor up (coarse) | `PageUp` |
@ -118,6 +125,10 @@ the keys in the "Global hotkeys" section can be used in any window, although not
| Expand selection to end of pattern | — | | Expand selection to end of pattern | — |
| Expand selection upwards (coarse) | `Shift-PageUp` | | Expand selection upwards (coarse) | `Shift-PageUp` |
| Expand selection downwards (coarse) | `Shift-PageDown` | | Expand selection downwards (coarse) | `Shift-PageDown` |
| Move selection up by one | `Alt-Up` |
| Move selection down by one | `Alt-Down` |
| Move selection to previous channel | `Alt-Left` |
| Move selection to next channel | `Alt-Right` |
| Delete | `Delete` | | Delete | `Delete` |
| Pull delete | `Backspace` | | Pull delete | `Backspace` |
| Insert | `Insert` | | Insert | `Insert` |
@ -143,102 +154,103 @@ the keys in the "Global hotkeys" section can be used in any window, although not
| Clear note input latch | — | | Clear note input latch | — |
| | | | | |
| **Instrument list** | | | **Instrument list** | |
| Add | `Insert` | | Add instrument | `Insert` |
| Duplicate | `Ctrl-D` | | Duplicate instrument | `Ctrl-D` |
| Open | — | | Open instrument | — |
| Open (replace current) | — | | Open instrument (replace current) | — |
| Save | — | | Save instrument | — |
| Save (.dmp) | — | | Save instrument (.dmp) | — |
| Move up | `Shift-Up` | | Move instrument up in list | `Shift-Up` |
| Move down | `Shift-Down` | | Move instrument down in list | `Shift-Down` |
| Delete | — | | Delete instrument | — |
| Edit | `Shift-Return` | | Edit instrument | `Shift-Return` |
| Cursor up | `Up` | | Instrument cursor up | `Up` |
| Cursor down | `Down` | | Instrument cursor down | `Down` |
| Toggle folders/standard view | `Ctrl-V` | | Instruments: toggle folders/standard view | `Ctrl-V` |
| | | | | |
| **Wavetable list** | | | **Wavetable list** | |
| Add | `Insert` | | Add wavetable | `Insert` |
| Duplicate | `Ctrl-D` | | Duplicate wavetable | `Ctrl-D` |
| Open | — | | Open wavetable | — |
| Open (replace current) | — | | Open wavetable (replace current) | — |
| Save | — | | Save wavetable | — |
| Save (.dmw) | — | | Save wavetable (.dmw) | — |
| Save (raw) | — | | Save wavetable (raw) | — |
| Move up | `Shift-Up` | | Move wavetable up in list | `Shift-Up` |
| Move down | `Shift-Down` | | Move wavetable down in list | `Shift-Down` |
| Delete | — | | Delete wavetable | — |
| Edit | `Shift-Return` | | Edit wavetable | `Shift-Return` |
| Cursor up | `Up` | | Wavetable cursor up | `Up` |
| Cursor down | `Down` | | Wavetable cursor down | `Down` |
| Toggle folders/standard view | `Ctrl-V` | | Wavetables: toggle folders/standard view | `Ctrl-V` |
| | | | | |
| **Sample list** | | | **Sample list** | |
| Add | `Insert` | | Add sample | `Insert` |
| Duplicate | `Ctrl-D` | | Duplicate sample | `Ctrl-D` |
| Create wavetable from selection | `Ctrl-W` | | Sample Editor: Create wavetable from selection | `Ctrl-W` |
| Open | — | | Open sample | — |
| Open (replace current) | — | | Open sample (replace current) | — |
| Import raw data | — | | Import raw sample data | — |
| Import raw data (replace current) | — | | Import raw sample data (replace current) | — |
| Save | — | | Save sample | — |
| Save (raw) | — | | Save sample (raw) | — |
| Move up | `Shift-Up` | | Move sample up in list | `Shift-Up` |
| Move down | `Shift-Down` | | Move sample down in list | `Shift-Down` |
| Delete | — | | Delete sample | — |
| Edit | `Shift-Return` | | Edit sample | `Shift-Return` |
| Cursor up | `Up` | | Sample cursor up | `Up` |
| Cursor down | `Down` | | Sample cursor down | `Down` |
| Preview | — | | Sample Preview | — |
| Stop preview | — | | Stop sample preview | — |
| Toggle folders/standard view | `Ctrl-V` | | Samples: Toggle folders/standard view | `Ctrl-V` |
| Samples: Make me a drum kit | — |
| | | | | |
| **Orders** | | | **Orders** | |
| Previous order | `Up` | | Previous order | `Up` |
| Next order | `Down` | | Next order | `Down` |
| Cursor left | `Left` | | Order cursor left | `Left` |
| Cursor right | `Right` | | Order cursor right | `Right` |
| Increase value | — | | Increase order value | — |
| Decrease value | — | | Decrease order value | — |
| Switch edit mode | — | | Switch order edit mode | — |
| Toggle alter entire row | `Ctrl-L` | | Order: Toggle alter entire row | `Ctrl-L` |
| Add | `Insert` | | Add order | `Insert` |
| Duplicate | `Ctrl-D` | | Duplicate order | `Ctrl-D` |
| Deep clone | `Ctrl-Shift-D` | | Deep clone order | `Ctrl-Shift-D` |
| Duplicate to end of song | `Ctrl-E` | | Copy current order to end of song | `Ctrl-E` |
| Deep clone to end of song | `Ctrl-Shift-E` | | Deep clone current order to end of song | `Ctrl-Shift-E` |
| Remove | `Delete` | | Remove order | `Delete` |
| Move up | `Shift-Up` | | Move order up | `Shift-Up` |
| Move down | `Shift-Down` | | Move order down | `Shift-Down` |
| Replay | — | | Replay order | — |
| | | | | |
| **Sample editor** | | | **Sample editor** | |
| Edit mode: Select | `Shift-I` | | Sample editor mode: Select | `Shift-I` |
| Edit mode: Draw | `Shift-D` | | Sample editor mode: Draw | `Shift-D` |
| Cut | `Ctrl-X` | | Sample editor: Cut | `Ctrl-X` |
| Copy | `Ctrl-C` | | Sample editor: Copy | `Ctrl-C` |
| Paste | `Ctrl-V` | | Sample editor: Paste | `Ctrl-V` |
| Paste replace | `Ctrl-Shift-V` | | Sample editor: Paste replace | `Ctrl-Shift-V` |
| Paste mix | `Ctrl-Alt-V` | | Sample editor: Paste mix | `Ctrl-Alt-V` |
| Select all | `Ctrl-A` | | Sample editor: Select all | `Ctrl-A` |
| Resize | `Ctrl-R` | | Sample editor: Resize | `Ctrl-R` |
| Resample | `Ctrl-E` | | Sample editor: Resample | `Ctrl-E` |
| Amplify | `Ctrl-B` | | Sample editor: Amplify | `Ctrl-B` |
| Normalize | `Ctrl-N` | | Sample editor: Normalize | `Ctrl-N` |
| Fade in | `Ctrl-I` | | Sample editor: Fade in | `Ctrl-I` |
| Fade out | `Ctrl-O` | | Sample editor: Fade out | `Ctrl-O` |
| Insert silence | `Insert` | | Sample editor: Insert silence | `Insert` |
| Apply silence | `Shift-Delete` | | Sample editor: Apply silence | `Shift-Delete` |
| Delete | `Delete` | | Sample editor: Delete | `Delete` |
| Trim | `Ctrl-Delete` | | Sample editor: Trim | `Ctrl-Delete` |
| Reverse | `Ctrl-T` | | Sample editor: Reverse | `Ctrl-T` |
| Invert | `Ctrl-Shift-T` | | Sample editor: Invert | `Ctrl-Shift-T` |
| Signed/unsigned exchange | `Ctrl-U` | | Sample editor: Signed/unsigned exchange | `Ctrl-U` |
| Apply filter | `Ctrl-F` | | Sample editor: Apply filter | `Ctrl-F` |
| Preview sample | — | | Sample editor: Preview sample | — |
| Stop sample preview | — | | Sample editor: Stop sample preview | — |
| Zoom in | `Ctrl-=` | | Sample editor: Zoom in | `Ctrl-=` |
| Zoom out | `Ctrl--` | | Sample editor: Zoom out | `Ctrl--` |
| Toggle auto-zoom | `Ctrl-0` | | Sample editor: Toggle auto-zoom | `Ctrl-0` |
| Create instrument from sample | — | | Sample editor: Create instrument from sample | — |
| Set loop to selection | `Ctrl-L` | | Sample editor: Set loop to selection | `Ctrl-L` |

View file

@ -2,24 +2,20 @@
the menu bar allows you to select from five menus: file, edit, settings, window and help. the menu bar allows you to select from five menus: file, edit, settings, window and help.
items in _italic_ don't appear in basic mode and are only available in advanced mode.
## file ## file
- **new...**: creates a new song. - **new...**: opens the new song dialog to choose a system.
- click a system name to create a new song with it.
- some systems have several variants, which are inside a group.
- **open...**: opens the file picker, allowing you to select a song to open. - **open...**: opens the file picker, allowing you to select a song to open.
- see [file formats](formats.md) for a list of formats Furnace is able to open. - see [file formats](formats.md) for a list of formats Furnace is able to open.
- **open recent**: contains a list of the songs you've opened before. - **open recent**: contains a list of the songs you've opened before.
- **clear history**: erases the file history. - **clear history**: erases the file history.
- **save**: saves the current song. - **save**: saves the current song.
- opens the file picker if this is a new song, or a backup. - opens the file picker if this is a new song, or a backup.
- **save as...**: opens the file picker, allowing you to save the song under a different name. - **save as...**: opens the file picker, allowing you to save the song under a different name.
- **export...**: allows you to export your song into other formats, such as audio files, VGM and more. see the [export](export.md) page for more information.
- **export**: allows you to export your song into other formats, such as audio files, VGM and more. see the [export](export.md) page for more information.
- **manage chips**: opens the [Chip Manager](../8-advanced/chip-manager.md) dialog. - **manage chips**: opens the [Chip Manager](../8-advanced/chip-manager.md) dialog.
- **restore backup**: restores a previously saved backup. - **restore backup**: restores a previously saved backup.
- Furnace keeps up to 5 backups of a song. - Furnace keeps up to 5 backups of a song.
- the backup directory is located in: - the backup directory is located in:
@ -28,21 +24,18 @@ items in _italic_ don't appear in basic mode and are only available in advanced
- Linux/other: `~/.config/furnace/backups` - Linux/other: `~/.config/furnace/backups`
- this directory grows in size as you use Furnace. remember to delete old backups periodically to save space. - this directory grows in size as you use Furnace. remember to delete old backups periodically to save space.
- **do NOT rely on the backup system as auto-save!** you should save a restored backup because Furnace will not save backups of backups. - **do NOT rely on the backup system as auto-save!** you should save a restored backup because Furnace will not save backups of backups.
- **exit**: closes Furnace. - **exit**: closes Furnace.
## edit ## edit
- **...**: does nothing except prevent accidental clicks on later menu items if the menu is too tall to fit on the program window. - **...**: 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. - **undo**: reverts the last action.
- **redo**: repeats what you undid previously. - **redo**: repeats what you undid previously.
- **cut**: moves the current selection in the pattern view to clipboard. - **cut**: moves the current selection in the pattern view to clipboard.
- **copy**: copies the current selection in the pattern view to clipboard. - **copy**: copies the current selection in the pattern view to clipboard.
- **paste**: inserts the clipboard's contents in the cursor position. - **paste**: inserts the clipboard's contents in the cursor position.
- you may be able to paste from OpenMPT as well. - you may be able to paste from OpenMPT as well.
- _**paste special...**:_ variants of the paste feature. - **paste special...**: variants of the paste feature.
- **paste mix**: inserts the clipboard's contents in the cursor position, but does not erase the occupied region. - **paste mix**: inserts the clipboard's contents in the cursor position, but does not erase the occupied region.
- **paste mix (background)**: does the same thing as paste mix, but doesn't alter content which is already there. - **paste mix (background)**: does the same thing as paste mix, but doesn't alter content which is already there.
- **paste with ins (foreground)**: same thing as paste mix, but changes the instrument. - **paste with ins (foreground)**: same thing as paste mix, but changes the instrument.
@ -55,81 +48,84 @@ items in _italic_ don't appear in basic mode and are only available in advanced
- if the selection is tall, it will select the entire column. - if the selection is tall, it will select the entire column.
- if a column is already selected, it will select the entire channel. - if a column is already selected, it will select the entire channel.
- if a channel is already selected, it will select the entire pattern. - if a channel is already selected, it will select the entire pattern.
- **operation mask**: toggles which columns will be affected by the listed operations. [more information here.](../8-advanced/opmask.md)
- _**operation mask**:_ toggles which columns will be affected by the listed operations. [more information here.](../8-advanced/opmask.md) - **input latch**: determines which data are placed along with a note. [more information here.](../8-advanced/inputlatch.md)
- _**input latch**:_ determines which data are placed along with a note. [more information here.](../8-advanced/inputlatch.md)
- **note/octave up/down**: transposes notes in the current selection. - **note/octave up/down**: transposes notes in the current selection.
- **values up/down**: changes values in the current selection by ±1 or ±16. - **values up/down**: changes values in the current selection by ±1 or ±16.
- **transpose**: transpose notes or change values by a specific amount. - **transpose**: transpose notes or change values by a specific amount.
- **interpolate**: fills in gaps in the selection by interpolation between values. - **interpolate**: fills in gaps in the selection by interpolation between values.
- **change instrument**: changes the instrument number in a selection. - **change instrument...**: changes the instrument number in a selection.
- **gradient/fade**: replace the selection with a "gradient" that goes from the beginning of the selection to the end. - **gradient/fade...**: replace the selection with a "gradient" that goes from the beginning of the selection to the end.
- does not affect the note column. - does not affect the note column.
- **Nibble mode**: when enabled, the fade will be per-nibble (0 to F) rather than per-value (00 to FF). - **Nibble mode**: when enabled, the fade will be per-nibble (0 to F) rather than per-value (00 to FF).
- use for effects like `04xy` (vibrato). - use for effects like `04xy` (vibrato).
- **scale**: scales values in the selection by a specific amount. - **scale...**: scales values in the selection by a specific amount.
- use to change volume in a selection for example. - use to change volume in a selection for example.
- **randomize**: replaces the selection with random values. - **randomize**: replaces the selection with random values.
- does not affect the note column. - does not affect the note column.
- **Nibble mode**: when enabled, the randomization will be per-nibble (0 to F) rather than per-value (00 to FF).
- **invert values**: `00` becomes `FF`, `01` becomes `FE`, `02` becomes `FD` and so on. - **invert values**: `00` becomes `FF`, `01` becomes `FE`, `02` becomes `FD` and so on.
- **flip selection**: flips the selection so it is backwards. - **flip selection**: flips the selection so it is backwards.
- **collapse/expand amount**: allows you to specify how much to collapse/expand in the next two menu items. - **collapse/expand amount**: allows you to specify how much to collapse/expand in the next two menu items.
- **collapse**: shrinks the selected contents. - **collapse**: shrinks the selected contents.
- **expand**: expands the selected contents. - **expand**: expands the selected contents.
- **collapse pattern**: same as collapse, but affects the entire pattern. - **collapse pattern**: same as collapse, but affects the entire pattern.
- **expand pattern**: same as expand, but affects the entire pattern. - **expand pattern**: same as expand, but affects the entire pattern.
- **collapse song**: same as collapse, but affects the entire song. - **collapse song**: same as collapse, but affects the entire song.
- it also changes speeds and pattern length to compensate. - it also changes speeds and pattern length to compensate.
- **expand song**: same as expand, but affects the entire song. - **expand song**: same as expand, but affects the entire song.
- it also changes speeds and pattern length to compensate. - it also changes speeds and pattern length to compensate.
- **find/replace**: shows [the Find/Replace window](../8-advanced/find-replace.md).
- _**find/replace**:_ shows [the Find/Replace window](../8-advanced/find-replace.md). - **clear...**: opens a window that allows you to mass-delete things like songs, unused instruments, and the like.
- **clear**: opens a window that allows you to mass-delete things like songs, unused instruments, and the like.
## settings ## settings
- **full screen**: expands the Furnace window so it covers your screen. - **full screen**: expands the Furnace window so it covers your screen.
- **lock layout**: prevents you from dragging/resizing docked windows, or docking more. - **lock layout**: prevents you from dragging/resizing docked windows, or docking more.
- **basic mode**: toggles [Basic Mode](basic-mode.md). - **pattern visualizer**: toggles pattern view particle effects when the song plays.
- **visualizer**: toggles pattern view particle effects when the song plays.
- **reset layout**: resets the workspace to its defaults. - **reset layout**: resets the workspace to its defaults.
- **settings...**: shows the Settings window. these are detailed in [settings.md]. - **user systems...**: shows the User Systems window. this is detailed in [the User Systems documentation](../8-advanced/user-systems.md).
- **settings...**: shows the Settings window. these are detailed in [the Settings documentation](settings.md).
## window ## window
all these menu items show or hide their associated windows. all these menu items show or hide their associated windows.
- [song information](song-info.md) - song
- [subsongs](song-info.md) - **[song comments](../8-advanced/comments.md)**
- [speed](song-info.md) - **[song information](song-info.md)**
- [instruments](../4-instrument/README.md) - **[subsongs](song-info.md)**
- [wavetables](../5-wave/README.md) - **[channels](../8-advanced/channels.md)**
- [samples](../6-sample/README.md) - **[chip manager](../8-advanced/chip-manager.md)**
- [orders](order-list.md) - **[orders](order-list.md)**
- [pattern](../3-pattern/README.md) - **[pattern](../3-pattern/README.md)**
- _[mixer](../8-advanced/mixer.md)_ - **[pattern manager](../8-advanced/pat-manager.md)**
- _[grooves](../8-advanced/grooves.md)_ - **[mixer](../8-advanced/mixer.md)**
- _[channels](../8-advanced/channels.md)_ - **[compatibility flags](../8-advanced/compat-flags.md)**
- _[pattern manager](../8-advanced/pat-manager.md)_ - assets
- _[chip manager](../8-advanced/chip-manager.md)_ - **[instruments](../4-instrument/README.md)**
- _[compatibility flags](../8-advanced/compat-flags.md)_ - **[samples](../6-sample/README.md)**
- [song comments](../8-advanced/comments.md) - **[wavetables](../5-wave/README.md)**
- **[instrument editor](../4-instrument/README.md)**
- [piano](../8-advanced/piano.md) - **[sample editor](../6-sample/README.md)**
- [oscilloscope](../8-advanced/osc.md) - **[wavetable editor](../5-wave/README.md)**
- [oscilloscopes (per-channel)](../8-advanced/chanosc.md) - visualizers
- [clock](../8-advanced/clock.md) - **[oscilloscope](../8-advanced/osc.md)**
- [register view](../8-advanced/regview.md) - **[oscilloscope (per-channel)](../8-advanced/chanosc.md)**
- [log viewer](../8-advanced/log-viewer.md) - **[oscilloscope (X-Y)](../8-advanced/xyosc.md)**
- [stats](../8-advanced/stats.md) - volume meter
- tempo
- **[clock](../8-advanced/clock.md)**
- **[grooves](../8-advanced/grooves.md)**
- **[speed](song-info.md)**
- debug
- **[log viewer](../8-advanced/log-viewer.md)**
- **[register view](../8-advanced/regview.md)**
- **[statistics](../8-advanced/stats.md)**
- **[memory composition](../8-advanced/memory-composition.md)**
- **[effect list](../3-pattern/effects.md)**
- **[play/edit controls](play-edit-controls.md)**
- **[piano/input pad](../8-advanced/piano.md)**
## help ## help

View file

@ -13,6 +13,7 @@ the "Play/Edit Controls" are used to control playback and change parameters of t
- **Poly**: turns on polyphony for previewing notes. toggles to **Mono** for monophony (one note at a time only). - **Poly**: turns on polyphony for previewing notes. toggles to **Mono** for monophony (one note at a time only).
- **Octave**: sets current input octave. - **Octave**: sets current input octave.
- **Step**: sets edit step. if this is 1, entering a note or effect will move to the next row. if this is a larger number, rows will be skipped. if this is 0, the cursor will stay in place. - **Step**: sets edit step. if this is 1, entering a note or effect will move to the next row. if this is a larger number, rows will be skipped. if this is 0, the cursor will stay in place.
- if clicked, Step becomes **Coarse**, which sets the number of rows moved with `PgUp`, `PgDn`, and related movement shortcuts. clicking again will revert it to Step.
- **Follow orders**: if on, the selected order in the orders window will follow the song during playback. - **Follow orders**: if on, the selected order in the orders window will follow the song during playback.
- **Follow pattern**: if on, the cursor will follow playback and the song will scroll by as it plays. - **Follow pattern**: if on, the cursor will follow playback and the song will scroll by as it plays.

View file

@ -1,25 +1,31 @@
# settings # settings
the Settings window allows you to change Furnace setting. the Settings window allows you to change Furnace settings.
settings are saved when clicking the **OK** or **Apply** buttons at the bottom of the window, and when closing the program. several backups are kept in the Furnace settings directory.
settings are saved when clicking the **OK** or **Apply** buttons at the bottom of the window.
## General ## General
### Program ### Program
- **Language**: select the language used for the interface. some languages are incomplete, and are listed with their approximate completion percentage.
- **Render backend**: changing this may help with performace or compatibility issues. the available render backends are: - **Render backend**: changing this may help with performace or compatibility issues. the available render backends are:
- SDL Renderer: this was the only available render backend prior to the addition of dedicated OpenGL/DirectX backends in 0.6. default on macOS.
- it is slower than the other backends.
- DirectX 11: works with the majority of graphics chips/cards and is optimized specifically for Windows.
- DirectX 9: use if your hardware is incompatible with DirectX 11.
- OpenGL 3.0: works with the majority of graphics chips/cards (from 2010 onwards). default on Linux. - OpenGL 3.0: works with the majority of graphics chips/cards (from 2010 onwards). default on Linux.
- OpenGL 2.0: use if you have a card without OpenGL 3.0 support. - OpenGL 2.0: use if you have a card without OpenGL 3.0 support.
- OpenGL 1.1: use if your card doesn't even support OpenGL 2.0. - OpenGL 1.1: use if your card doesn't even support OpenGL 2.0.
- DirectX 11: works with the majority of graphics chips/cards and is optimized specifically for Windows.
- SDL Renderer: this was the only available render backend prior to the addition of dedicated OpenGL/DirectX backends in 0.6. default on macOS.
- it is slower than the other backends.
- Software: this is a last resort backend which renders the interface in software. very slow! - Software: this is a last resort backend which renders the interface in software. very slow!
- **Render driver**: this setting appears when using the SDL Renderer backend. it allows you to select an SDL render driver. - **Advanced render backend settings**: only applicable with some render backends.
- **Render driver**: this setting only appears when using the SDL Renderer backend. it allows you to select an SDL render driver.
- OpenGL settings: these only appear when using an OpenGL backend, and should only be adjusted if the display is incorrect.
- **VSync**: synchronizes rendering to VBlank and eliminates tearing. - **VSync**: synchronizes rendering to VBlank and eliminates tearing.
- **Frame rate limit**: allows you to set a frame rate limit (in frames per second). - **Frame rate limit**: allows you to set a frame rate limit (in frames per second).
- only has effect when VSync is off or not available (e.g. software rendering or force-disabled on driver settings). - only has effect when VSync is off or not available (e.g. software rendering or force-disabled on driver settings).
- **Display render time**: displays frame rate and frame render time at the right side of the menu bar.
- **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. - **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. - **Power-saving mode**: saves power by lowering the frame rate to 2fps when idle.
- may cause issues under Mesa drivers! - may cause issues under Mesa drivers!
@ -49,6 +55,9 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
- **Remember last values** - **Remember last values**
- **Store instrument name in .fui**: when enabled, saving an instrument will store its name. this may increase file size. - **Store instrument name in .fui**: when enabled, saving an instrument will store its name. this may increase file size.
- **Load instrument name from .fui**: when enabled, loading an instrument will use the stored name (if present). otherwise, it will use the file name. - **Load instrument name from .fui**: when enabled, loading an instrument will use the stored name (if present). otherwise, it will use the file name.
- **Auto-fill file name when saving**: pre-fill the file name field when saving or exporting.
- when saving a module, the existing file name will be auto-filled.
- when saving an instrument or sample, its name will be auto-filled.
### New Song ### New Song
@ -79,6 +88,11 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
- **New instruments are blank**: when enabled, adding FM instruments will make them blank (rather than loading the default one). - **New instruments are blank**: when enabled, adding FM instruments will make them blank (rather than loading the default one).
### Configuration
- **Import**: select an exported `.ini` config file to overwrite current settings.
- **Export**: select an `.ini` file to save current settings.
- **Factory Reset**: resets all settings to default and purges settings backups.
## Audio ## Audio
### Output ### Output
@ -100,9 +114,7 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
- **Sample rate**: audio output rate. - **Sample rate**: audio output rate.
- a lower rate decreases quality and isn't really beneficial. - a lower rate decreases quality and isn't really beneficial.
- if using PortAudio backend, be careful about this value. - if using PortAudio backend, be careful about this value.
- **Outputs**: number of audio outputs created, up to 16. - **Outputs**: number of audio outputs created, up to 16. default is 2 (stereo).
- only appears when Backend is JACK.
- **Channels**: mono, stereo or something.
- **Buffer size**: size of buffer in both samples and milliseconds. - **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 low value may cause stuttering/glitches in playback (known as "underruns" or "xruns").
- setting this to a high value increases latency. - setting this to a high value increases latency.
@ -135,6 +147,7 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
### MIDI input ### MIDI input
- **MIDI input**: input device. - **MIDI input**: input device.
- **Rescan MIDI devices**: repopulates list with all currently connected MIDI devices. useful if a device is connected while Furnace is running.
- **Note input**: enables note input. disable if you intend to use this device only for binding actions. - **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. - **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 MIDI channels to direct channels**: when enabled, notes from MIDI channels will be mapped to channels rather than the cursor position.
@ -291,6 +304,7 @@ below all the binds, select a key from the dropdown list to add it. it will appe
- **Push value when overwriting instead of clearing it**: in the order list and pattern editors, typing into an already-filled value will shift digits instead of starting fresh. for example: - **Push value when overwriting instead of clearing it**: in the order list and pattern editors, typing into an already-filled value will shift digits instead of starting fresh. for example:
- if off: moving the cursor onto the value `A5` and typing a "B" results in `0B`. - if off: moving the cursor onto the value `A5` and typing a "B" results in `0B`.
- if on: moving the cursor onto the value `A5` and typing a "B" results in `5B`. - if on: moving the cursor onto the value `A5` and typing a "B" results in `5B`.
- **Keyboard note/value input repeat (hold key to input continuously)**
- **Effect input behavior:** - **Effect input behavior:**
- **Move down**: after entering an effect (or effect value), the cursor moves down. - **Move down**: after entering an effect (or effect value), the cursor moves down.
- **Move to effect value (otherwise move down)**: after entering an effect, the cursor moves to its value. if entering a value, the cursor moves down. - **Move to effect value (otherwise move down)**: after entering an effect, the cursor moves to its value. if entering a value, the cursor moves down.
@ -359,11 +373,7 @@ below all the binds, select a key from the dropdown list to add it. it will appe
- **Pattern font** font for the pattern view, the order list, and related. - **Pattern font** font for the pattern view, the order list, and related.
- if "Custom...", a file path selector will appear. - if "Custom...", a file path selector will appear.
- **Size**: font size. - **Size**: font size.
- **Display Japanese characters**, **Display Chinese (Simplified) characters**, **Display Chinese (Traditional) characters** and **Display Korean characters**: only toggle these options if you have enough graphics memory.
- these are a temporary solution until dynamic font atlas is implemented in Dear ImGui.
#### FreeType-specific settings #### FreeType-specific settings
- **Anti-aliased fonts**: when enabled, fonts will be rendered smooth. - **Anti-aliased fonts**: when enabled, fonts will be rendered smooth.
- **Support bitmap fonts**: this option allows you to enable the loading of bitmap fonts. - **Support bitmap fonts**: this option allows you to enable the loading of bitmap fonts.
- be noted that this may force non-bitmap fonts to undesired sizes! - be noted that this may force non-bitmap fonts to undesired sizes!
@ -376,6 +386,13 @@ below all the binds, select a key from the dropdown list to add it. it will appe
- **Disable**: only rely upon font hinting data. - **Disable**: only rely upon font hinting data.
- **Enable**: prefer font hinting data if present. - **Enable**: prefer font hinting data if present.
- **Force**: ignore font hinting data. - **Force**: ignore font hinting data.
#### non-specific settings
- **Oversample**: renders the font internally at higher resolution for visual quality.
- higher settings use more video memory.
- for pixel or bitmap fonts, set this to **1x**.
- **Load fallback font**: load an extra font that contains nearly all characters that can be used, in case the selected fonts lack them. uses much video memory
- **Display Japanese characters**, **Display Chinese (Simplified) characters**, **Display Chinese (Traditional) characters** and **Display Korean characters**: only toggle these options if you have enough graphics memory.
- these are a temporary solution until dynamic font atlas is implemented in Dear ImGui.
### Program ### Program
@ -386,15 +403,16 @@ below all the binds, select a key from the dropdown list to add it. it will appe
- **/path/to/file.fur - Furnace** - **/path/to/file.fur - Furnace**
- **Display system name on title bar** - **Display system name on title bar**
- **Display chip names instead of "multi-system" in title bar** - **Display chip names instead of "multi-system" in title bar**
- **Export options layout:**
- **Sub-menus in File menu**: export options appear in the File menu as sub-menus.
- **Modal window with tabs**: a single "export..." option that opens a dialog with export options. this is the default.
- **Modal windows with options in File menu**: like Sub-menus in File menu, but instead of being sub-menus, selecting one opens a dialog with export settings.
- **Status bar:** - **Status bar:**
- **Cursor details** - **Cursor details**
- **File path** - **File path**
- **Cursor details or file path** - **Cursor details or file path**
- **Nothing** - **Nothing**
- **Display playback status when playing**: display playback time and current location in the menu bar.
- **Export options layout:**
- **Sub-menus in File menu**: export options appear in the File menu as sub-menus.
- **Modal window with tabs**: a single "export..." option that opens a dialog with export options. this is the default.
- **Modal windows with options in File menu**: like Sub-menus in File menu, but instead of being sub-menus, selecting one opens a dialog with export settings.
- **Capitalize menu bar** - **Capitalize menu bar**
- **Display add/configure/change/remove chip menus in File menu**: if enabled, the "manage chips" item in the file menu is split into the four listed items for quick access. - **Display add/configure/change/remove chip menus in File menu**: if enabled, the "manage chips" item in the file menu is split into the four listed items for quick access.
@ -532,10 +550,10 @@ below all the binds, select a key from the dropdown list to add it. it will appe
- **Rounded window corners** - **Rounded window corners**
- **Rounded buttons** - **Rounded buttons**
- **Rounded menu corners** - **Rounded menu corners**
- **Rounded tabs**
- **Rounded scrollbars**
- **Borders around widgets**: draws borders on buttons, checkboxes, text widgets, and the like. - **Borders around widgets**: draws borders on buttons, checkboxes, text widgets, and the like.
## Color ## Color
### Color scheme ### Color scheme
@ -544,9 +562,28 @@ below all the binds, select a key from the dropdown list to add it. it will appe
- **Export** - **Export**
- **Reset defaults** - **Reset defaults**
- **Guru mode**: exposes all color options (instead of accent colors). - **Guru mode**: exposes all color options (instead of accent colors).
- **General** - **Interface**
- **Frame shading**: applies a gradient effect to buttons and input boxes.
- **Color scheme type:** - **Color scheme type:**
- **Dark** - **Dark**
- **Light** - **Light**
- **Frame shading**: applies a gradient effect to buttons and input boxes. - **Accent colors**: select main interface colors.
- **Primary**
- **Secondary**
- several more categories... - several more categories...
## Backup
### Configuration
- **Enable backup system**: turn on automatic backups of the current open file.
- **Interval (in seconds)**: time between automatic backups.
- **Backups per file**: maximum number of backups to store for each file. oldest backups are deleted first.
### Backup Management
- **Purge before**:
- **Go**: purge all backups from before the selected date.
- total space used by all backups:
- **Refresh**: recalculate space.
- **Delete All**: purge all backups.

View file

@ -3,11 +3,11 @@
- **Name**: the track's title. - **Name**: the track's title.
- **Author**: the author(s) of this track. - **Author**: the author(s) of this track.
- **Album**: the associated album name (or the name of the game the song is from). - **Album**: the associated album name (or the name of the game the song is from).
- **System**: the game console or computer the track is designed for. this is automatically set when creating a new tune, but in advanced mode, it can be changed to anything one wants. the **Auto** button will provide a guess based on the chips in use. - **System**: the name of the game console or computer the track is designed for. this is automatically set when creating a new tune, but can be changed to anything. the **Auto** button will provide a guess based on the chips in use.
all of this metadata will be included in a VGM export. this isn't the case for an audio export, however. all of this metadata will be included in a VGM export. this isn't the case for an audio export, however.
- _**Tuning (A-4)**_: set tuning based on the note A-4, which should be 440 in most cases. opening an Amiga MOD will set it to 436 for hardware compatibility. available only in advanced mode. - **Tuning (A-4)**: set tuning based on the note A-4, which should be 440 in most cases. opening an Amiga MOD will set it to 436 for hardware compatibility.
## subsongs ## subsongs
@ -23,16 +23,17 @@ this window allows one to create **subsongs** - multiple individual songs within
there are multiple ways to set the tempo of a song. there are multiple ways to set the tempo of a song.
items in _italic_ don't appear in basic mode and are only available in advanced mode. **Base Tempo**: tempo in beats per minute (BPM). this is affected by the Highlight settings below.
- clicking the Base Tempo button switches to the more technical Tick Rate.
**Tick Rate**: the frequency of ticks per second, thus the rate at which notes and effects are processed. **Tick Rate**: the frequency of ticks per second, thus the rate at which notes and effects are processed.
- all values are allowed for all chips, though most chips have hardware limitations that mean they should stay at either 60 (approximately NTSC) or 50 (exactly PAL). - all values are allowed for all chips, though most chips have hardware limitations that mean they should stay at either 60 (approximately NTSC) or 50 (exactly PAL).
- clicking the Tick Rate button switches to a more traditional **Base Tempo** BPM setting. - clicking the Tick Rate button switches to the more traditional Base Tempo BPM setting.
**Speed**: the number of ticks per row. **Speed**: the number of ticks per row.
- clicking the "Speed" button changes to more complex modes covered in the [grooves](../8-advanced/grooves.md) page. - clicking the "Speed" button changes to more complex modes covered in the [grooves](../8-advanced/grooves.md) page.
_**Virtual Tempo**:_ Simulates any arbitrary tempo without altering the tick rate. it does this by adding or skipping ticks to approximate the tempo. the two numbers represent a ratio applied to the actual tick rate. example: **Virtual Tempo**: simulates any arbitrary tempo without altering the tick rate. it does this by adding or skipping ticks to approximate the tempo. the two numbers represent a ratio applied to the actual tick rate. example:
- set tick rate to 150 BPM (60 Hz) and speed to 6. - set tick rate to 150 BPM (60 Hz) and speed to 6.
- set the first virtual tempo number (numerator) to 200. - set the first virtual tempo number (numerator) to 200.
- set the second virtual tempo number (denominator) to 150. - set the second virtual tempo number (denominator) to 150.
@ -40,7 +41,8 @@ _**Virtual Tempo**:_ Simulates any arbitrary tempo without altering the tick rat
- the ratio doesn't have to match BPM numbers. set the numerator to 4 and the denominator to 5, and the virtual BPM becomes 150 × 4/5 = 120. - the ratio doesn't have to match BPM numbers. set the numerator to 4 and the denominator to 5, and the virtual BPM becomes 150 × 4/5 = 120.
- another way to accomplish this with more control over the results is to use grooves. see the page on [grooves](../8-advanced/grooves.md) for details. - another way to accomplish this with more control over the results is to use grooves. see the page on [grooves](../8-advanced/grooves.md) for details.
_**Divider**:_ Changes the effective tick rate. a tick rate of 60Hz and a divisor of 6 will result in ticks lasting a tenth of a second each! **Divider**: changes the effective tick rate. a tick rate of 60Hz and a divisor of 6 will result in ticks lasting a tenth of a second each!
- to the right, the effective BPM is listed, taking all settings into account.
**Highlight**: sets the pattern row highlights: **Highlight**: sets the pattern row highlights:
- the first value represents the number of rows per beat. - the first value represents the number of rows per beat.
@ -50,4 +52,4 @@ _**Divider**:_ Changes the effective tick rate. a tick rate of 60Hz and a diviso
**Pattern Length**: the length of each pattern in rows. this affects all patterns in the song, and every pattern must be the same length. (Individual patterns can be cut short by `0Bxx`, `0Dxx`, and `FFxx` commands.) **Pattern Length**: the length of each pattern in rows. this affects all patterns in the song, and every pattern must be the same length. (Individual patterns can be cut short by `0Bxx`, `0Dxx`, and `FFxx` commands.)
_**Song Length**:_ how many orders are in the order list. decreasing it will hide the orders at the bottom. increasing it will restore those orders; increasing it further will add new orders of all `00` patterns. **Song Length**: how many orders are in the order list. decreasing it will hide the orders at the bottom. increasing it will restore those orders; increasing it further will add new orders of all `00` patterns.

View file

@ -119,6 +119,10 @@ Shift-Up | expand selection upwards
Shift-Down | expand selection downwards Shift-Down | expand selection downwards
Shift-Left | expand selection to the left Shift-Left | expand selection to the left
Shift-Right | expand selection to the right Shift-Right | expand selection to the right
Alt-Up | move selection up by one
Alt-Down | move selection down by one
Alt-Left | move selection to previous channel
Alt-Right | move selection to next channel
Backspace | delete note at cursor and/or pull pattern upwards (configurable) Backspace | delete note at cursor and/or pull pattern upwards (configurable)
Delete | delete selection Delete | delete selection
Insert | create blank row at cursor position and push pattern Insert | create blank row at cursor position and push pattern

View file

@ -89,6 +89,7 @@ in there, you can modify certain data pertaining to your sample, such as the:
- **Open**: replaces current sample. - **Open**: replaces current sample.
- right-clicking brings up a menu: - 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. - **import raw...**: brings up a file selector, then presents a dialog to choose the format of the selected file.
- **import raw (replace)...**: same as above, but instead of adding it to the sample list, it replaces the currently selected sample.
- **Save**: saves current sample to disk. - **Save**: saves current sample to disk.
- right-clicking brings up a menu: - right-clicking brings up a menu:
- **save raw...**: brings up a file selector, then saves the sample as raw data. - **save raw...**: brings up a file selector, then saves the sample as raw data.

View file

@ -15,11 +15,11 @@ it features echo and up to 16 voices.
- `10xx`: **change wave.** - `10xx`: **change wave.**
- `11xy`: **configure echo.** - `11xy`: **configure echo.**
- this effect is kinda odd. this is how it works: - this effect is kinda odd. here's how to use it:
- create an empty instrument and put a very high note of it in channel 1.
> How do you echo on GBA - put `110x` in the effect column.
> - set volume column to set feedback.
> Create an empty instrment and put a very high note of it in channel 1 then do 110x in effect column and set volume column to set feedback and do nothing else on it - don't use the channel for anything else.
- `12xy`: **toggle invert.** - `12xy`: **toggle invert.**
- `x` left channel. - `x` left channel.

View file

@ -59,7 +59,7 @@ Furnace also allows the SNES to use wavetables (and the wavetable synthesizer) i
- `00` to `7F` for 0 to 127. - `00` to `7F` for 0 to 127.
- `80` to `FF` for -128 to -1. - `80` to `FF` for -128 to -1.
- note: be sure the sum of all coefficients is between -128 and 127. sums outside that may result in overflow and therefore clicking. - note: be sure the sum of all coefficients is between -128 and 127. sums outside that may result in overflow and therefore clicking.
- see [SnesLab](https://sneslab.net/wiki/FIR_Filter) for a full explanation and examples. - see SnesLab for [echo filter explanations and examples](https://sneslab.net/wiki/FIR_Filter#Uses).
## info ## info
@ -67,6 +67,8 @@ this chip uses the [SNES](../4-instrument/snes.md) instrument editor.
when two channels are joined for pitch modulation, the channel bar will show `mod` on a bracket tying them together. when two channels are joined for pitch modulation, the channel bar will show `mod` on a bracket tying them together.
when using sample offset commands, be sure to open each involved sample in the sample editor, look to the "Info" section at the top-left, and check the "no BRR filters" box. this prevents sound glitches, at the cost of lowering the sample quality to 4-bit.
## channel status ## channel status
the following icons are displayed when channel status is enabled in the pattern view: the following icons are displayed when channel status is enabled in the pattern view:
@ -93,6 +95,8 @@ the following options are available in the Chip Manager window:
- **Feedback**: sets how much of the echo output will be fed back into the buffer. - **Feedback**: sets how much of the echo output will be fed back into the buffer.
- **Echo volume**: sets echo volume. - **Echo volume**: sets echo volume.
- **Echo filter**: adjusts echo filter. - **Echo filter**: adjusts echo filter.
- **Dec/Hex**: toggles decimal or hexadecimal mode for the filter settings text entry box to the right.
- SnesLab provides [echo filter explanations and examples](https://sneslab.net/wiki/FIR_Filter#Uses). their example filter strings can be pasted directly into the filter settings text entry box if set to Hex mode.
## ADSR ## ADSR

View file

@ -8,22 +8,27 @@ as listed in the "Edit" menu:
as listed in the "Window" menu: as listed in the "Window" menu:
- [mixer](mixer.md) - song
- [grooves](grooves.md) - [song comments](../8-advanced/comments.md)
- [channel manager](channels.md) - [channels](../8-advanced/channels.md)
- [pattern manager](pat-manager.md) - [chip manager](../8-advanced/chip-manager.md)
- [chip manager](chip-manager.md) - [pattern manager](../8-advanced/pat-manager.md)
- [compatibility flags](compat-flags.md) - [mixer](../8-advanced/mixer.md)
- [song comments](comments.md) - [compatibility flags](../8-advanced/compat-flags.md)
- visualizers
- [piano](piano.md) - [oscilloscope](../8-advanced/osc.md)
- [oscilloscope](osc.md) - [oscilloscope (per-channel)](../8-advanced/chanosc.md)
- [oscilloscope (X-Y)](xyosc.md) - [oscilloscope (X-Y)](../8-advanced/xyosc.md)
- [oscilloscopes (per-channel)](chanosc.md) - volume meter
- [clock](clock.md) - tempo
- [register view](regview.md) - [clock](../8-advanced/clock.md)
- [log viewer](log-viewer.md) - [grooves](../8-advanced/grooves.md)
- [stats](stats.md) - debug
- [log viewer](../8-advanced/log-viewer.md)
- [register view](../8-advanced/regview.md)
- [statistics](../8-advanced/stats.md)
- [memory composition](../8-advanced/memory-composition.md)
- [piano/input pad](../8-advanced/piano.md)
other: other:

View file

@ -5,8 +5,9 @@ the "Channels" dialog allows manipulation of the song's channels.
![channels dialog](channels.png) ![channels dialog](channels.png)
each channel has the following options: each channel has the following options:
- **Visible**: uncheck the box to hide the channel from the pattern view. pattern data will be kept. - **Pat**: uncheck the box to hide the channel from the pattern view. pattern data will be kept.
- crossed-arrows button: click and drag to rearrange pattern data throughout the song. - **Osc**: uncheck the box to hide the channel from the per-channel oscilloscope view.
- **Swap**: click and drag to rearrange pattern data throughout the song.
- note: this does **not** move channels around! it only moves the channel's pattern data. - note: this does **not** move channels around! it only moves the channel's pattern data.
- **Name**: the name displayed at the top of each channel in the pattern view. - **Name**: the name displayed at the top of each channel in the pattern view.
- the next setting is "short name", which is displayed in the orders view and/or when a channel is collapsed. - the next setting is "short name", which is displayed in the orders view and/or when a channel is collapsed.

View file

@ -14,6 +14,7 @@ right-clicking the view will display the configuration view shown above:
- **Mode 2**: bias slightly toward more columns. - **Mode 2**: bias slightly toward more columns.
- **Mode 3**: always more columns than rows. - **Mode 3**: always more columns than rows.
- **Amplitude**: scales amplitude for all oscilloscope views. - **Amplitude**: scales amplitude for all oscilloscope views.
- **Line size**: controls line thickness.
- **Gradient**: this allows you to use a gradient for determining the waveforms' colors instead of a single color. see the gradient section for more information. - **Gradient**: this allows you to use a gradient for determining the waveforms' colors instead of a single color. see the gradient section for more information.
- if this option is off, a color selector will be displayed. right-click on it for some options: - if this option is off, a color selector will be displayed. right-click on it for some options:
- select between the square selector and the color wheel selector. - select between the square selector and the color wheel selector.

View file

@ -8,6 +8,8 @@ the **chip manager** window allows you to manage chips, including adding, changi
**Clone channel data**: when cloning chips, also copy patterns, pattern names, channel names and other parameters to the clone. **Clone channel data**: when cloning chips, also copy patterns, pattern names, channel names and other parameters to the clone.
**Clone at end**: instead of inserting the clone directly after the cloned chip, add it to the end.
to move a chip around, click and drag the ![crossed-arrows](chip-manager-move.png) button to the left. to move a chip around, click and drag the ![crossed-arrows](chip-manager-move.png) button to the left.
to duplicate a chip, click the **Clone** button. to duplicate a chip, click the **Clone** button.

View file

@ -0,0 +1,22 @@
# user systems
combinations of chips and chip configurations can be stored as **user systems** presets that are easily accessed when starting a new song.
![user systems window](user-systems.png)
the `+` button at the top of the **Systems** list will add a new system.
next to the **Name** field, the **Remove** button removes the current system from the list.
chip configuration is exactly as in the [chip manager](chip-manager.md) window.
the **Advanced** field stores additional settings that are set when a new song is started. these are listed in "option=value" format, one per line.
- `tickRate`: sets tick rate.
**Save and Close**: as it says.
**Import**: opens a dialog to select a `.cfgu` file, then adds its systems to the list.
**Import (replace)**: opens a similar dialog, then clears the existing systems list and replaces it with the imported one.
**Export**: stores the current list of systems in a selected `.cfgu` file.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -620,6 +620,17 @@ class DivEngine {
void loadWOPL(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath); void loadWOPL(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadWOPN(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath); void loadWOPN(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
//sample banks
void loadP(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPPC(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPPS(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPVI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPDX(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPZI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadP86(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret); int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
bool initAudioBackend(); bool initAudioBackend();
@ -1034,7 +1045,8 @@ class DivEngine {
int addSamplePtr(DivSample* which); int addSamplePtr(DivSample* which);
// get sample from file // get sample from file
DivSample* sampleFromFile(const char* path); //DivSample* sampleFromFile(const char* path);
std::vector<DivSample*> sampleFromFile(const char* path);
// get raw sample // get raw sample
DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles, int rate); DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles, int rate);

View file

@ -63,3 +63,34 @@ enum DivFurVariants: int {
DIV_FUR_VARIANT_VANILLA=0, DIV_FUR_VARIANT_VANILLA=0,
DIV_FUR_VARIANT_B=1, DIV_FUR_VARIANT_B=1,
}; };
// MIDI-related
struct midibank_t {
String name;
uint8_t bankMsb,
bankLsb;
};
// Reused patch data structures
// SBI and some other OPL containers
struct sbi_t {
uint8_t Mcharacteristics,
Ccharacteristics,
Mscaling_output,
Cscaling_output,
Meg_AD,
Ceg_AD,
Meg_SR,
Ceg_SR,
Mwave,
Cwave,
FeedConnect;
};
//bool stringNotBlank(String& str);
// detune needs extra translation from register to furnace format
//uint8_t fmDtRegisterToFurnace(uint8_t&& dtNative);
//void readSbiOpData(sbi_t& sbi, SafeReader& reader);

View file

@ -639,6 +639,8 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
logD("seek not needed..."); logD("seek not needed...");
} }
logV("reading sample data (%d)",s->samples);
if (flags&8) { // compressed sample if (flags&8) { // compressed sample
unsigned int ret=0; unsigned int ret=0;
logV("decompression begin... (%d)",s->samples); logV("decompression begin... (%d)",s->samples);
@ -672,62 +674,66 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
} }
logV("got: %d",ret); logV("got: %d",ret);
} else { } else {
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { try {
if (flags&4) { // downmix stereo if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
for (unsigned int i=0; i<s->samples; i++) { if (flags&4) { // downmix stereo
short l; for (unsigned int i=0; i<s->samples; i++) {
if (convert&2) { short l;
l=reader.readS_BE(); if (convert&2) {
} else { l=reader.readS_BE();
l=reader.readS(); } else {
l=reader.readS();
}
if (!(convert&1)) {
l^=0x8000;
}
s->data16[i]=l;
} }
if (!(convert&1)) { for (unsigned int i=0; i<s->samples; i++) {
l^=0x8000; short r;
if (convert&2) {
r=reader.readS_BE();
} else {
r=reader.readS();
}
if (!(convert&1)) {
r^=0x8000;
}
s->data16[i]=(s->data16[i]+r)>>1;
} }
s->data16[i]=l; } else {
} for (unsigned int i=0; i<s->samples; i++) {
for (unsigned int i=0; i<s->samples; i++) { if (convert&2) {
short r; s->data16[i]=reader.readS_BE()^((convert&1)?0:0x8000);
if (convert&2) { } else {
r=reader.readS_BE(); s->data16[i]=reader.readS()^((convert&1)?0:0x8000);
} else { }
r=reader.readS();
} }
if (!(convert&1)) {
r^=0x8000;
}
s->data16[i]=(s->data16[i]+r)>>1;
} }
} else { } else {
for (unsigned int i=0; i<s->samples; i++) { if (flags&4) { // downmix stereo
if (convert&2) { for (unsigned int i=0; i<s->samples; i++) {
s->data16[i]=reader.readS_BE()^((convert&1)?0:0x8000); signed char l=reader.readC();
} else { if (!(convert&1)) {
s->data16[i]=reader.readS()^((convert&1)?0:0x8000); l^=0x80;
}
s->data8[i]=l;
}
for (unsigned int i=0; i<s->samples; i++) {
signed char r=reader.readC();
if (!(convert&1)) {
r^=0x80;
}
s->data8[i]=(s->data8[i]+r)>>1;
}
} else {
for (unsigned int i=0; i<s->samples; i++) {
s->data8[i]=reader.readC()^((convert&1)?0:0x80);
} }
} }
} }
} else { } catch (EndOfFileException& e) {
if (flags&4) { // downmix stereo logW("premature end of file...");
for (unsigned int i=0; i<s->samples; i++) {
signed char l=reader.readC();
if (!(convert&1)) {
l^=0x80;
}
s->data8[i]=l;
}
for (unsigned int i=0; i<s->samples; i++) {
signed char r=reader.readC();
if (!(convert&1)) {
r^=0x80;
}
s->data8[i]=(s->data8[i]+r)>>1;
}
} else {
for (unsigned int i=0; i<s->samples; i++) {
s->data8[i]=reader.readC()^((convert&1)?0:0x80);
}
}
} }
} }

124
src/engine/fileOps/p.cpp Normal file
View file

@ -0,0 +1,124 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//P VOX ADPCM sample bank
/* =======================================
Header
=======================================
0x0000 - 0x03FF 256 * {
Sample start (uint32_t)
- 0x00000000 = unused
}
=======================================
Body
=======================================
Stream of Sample Data {
MSM6258 ADPCM encoding
nibble-swapped VOX / Dialogic ADPCM
Mono
Sample rate?
16000Hz seems fine
} */
#define P_BANK_SIZE 256
#define P_SAMPLE_RATE 16000
typedef struct
{
uint32_t start_pointer;
} P_HEADER;
void DivEngine::loadP(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
P_HEADER headers[P_BANK_SIZE];
for(int i = 0; i < P_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned int)reader.readI_BE();
}
for(int i = 0; i < P_BANK_SIZE; i++)
{
if(headers[i].start_pointer != 0)
{
DivSample* s = new DivSample;
s->rate = P_SAMPLE_RATE;
s->centerRate = P_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_VOX;
reader.seek((int)headers[i].start_pointer, SEEK_SET);
int sample_pos = 0;
int sample_len = 0;
if(i < P_BANK_SIZE - 1)
{
sample_len = headers[i + 1].start_pointer - headers[i].start_pointer;
}
else
{
sample_len = (int)reader.size() - headers[i].start_pointer;
}
if(sample_len > 0)
{
s->init(sample_len * 2);
for(int j = 0; j < sample_len; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
curr_byte = (curr_byte << 4) | (curr_byte >> 4);
s->dataVOX[sample_pos] = curr_byte;
sample_pos++;
}
ret.push_back(s);
logI("p: start %d len %d", headers[i].start_pointer, sample_len);
}
else
{
delete s;
}
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

142
src/engine/fileOps/p86.cpp Normal file
View file

@ -0,0 +1,142 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//P86 8-bit PCM sample bank
/* =======================================
Header
=======================================
0x0000 Identifier (12b)
"PCM86 DATA(\n)(\0)"
0x000C Targeted P86DRV version (1b)
version <high nibble>.<low nibble>
0x000D File Length (3b)
0x0010 - 0x060F 256 * {
Pointer to Sample Data Start (3b)
Length of Sample Data (3b)
(0x000000 0x000000 -> no sample for this instrument ID)
}
=======================================
Body
=======================================
Stream of Sample Data {
8-Bit Signed
Mono
16540Hz
(above sample rate according to KAJA's documentation
any sample rate possible, for different base note & octave)
} */
#define P86_BANK_SIZE 256
#define P86_SAMPLE_RATE 16540
#define P86_FILE_SIG "PCM86 DATA\n\0"
typedef struct
{
uint32_t start_pointer;
uint32_t sample_length;
} P86_HEADER;
#define UNUSED(x) (void)(x)
uint32_t read_3bytes(SafeReader& reader)
{
unsigned char arr[3];
for (int i = 0; i < 3; i++)
{
arr[i] = (unsigned char)reader.readC();
}
return (arr[0] | (arr[1] << 8) | (arr[2] << 16));
}
void DivEngine::loadP86(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
P86_HEADER headers[P86_BANK_SIZE];
String file_sig = reader.readString(12);
if(file_sig != P86_FILE_SIG) return;
uint8_t version = reader.readC();
UNUSED(version);
uint32_t file_size = read_3bytes(reader);
UNUSED(file_size);
for(int i = 0; i < P86_BANK_SIZE; i++)
{
headers[i].start_pointer = read_3bytes(reader);
headers[i].sample_length = read_3bytes(reader);
}
for(int i = 0; i < P86_BANK_SIZE; i++)
{
if(headers[i].start_pointer != 0 && headers[i].sample_length != 0)
{
DivSample* s = new DivSample;
s->rate = P86_SAMPLE_RATE;
s->centerRate = P86_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_8BIT;
s->init(headers[i].sample_length); //byte per sample
reader.seek((int)headers[i].start_pointer, SEEK_SET);
int sample_pos = 0;
for(uint32_t j = 0; j < headers[i].sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
//curr_byte += 0x80;
s->data8[sample_pos] = curr_byte;
sample_pos++;
}
ret.push_back(s);
logI("p86: start %06X len %06X", headers[i].start_pointer, headers[i].sample_length);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

101
src/engine/fileOps/pdx.cpp Normal file
View file

@ -0,0 +1,101 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PDX 8-bit OKI ADPCM sample bank
/* File format
The file starts with a header with 96 8-byte pairs, with each pair containing offset and length of ADPCM data chunks for each note value, like so:
[[byte pointer to sample in file -- 32-bit unsigned integer (4 bytes)] [empty: $0000 -- 16-bit integer] [length of sample, amount in bytes -- 16-bit unsigned integer] ×96] [ADPCM data] EOF
The first sample (1) is mapped to 0x80 (= the lowest note within MXDRV) and the last sample (96) is mapped to 0xDF (= the highest note within MXDRV).
Samples are encoded in 4-bit OKI ADPCM encoded nibbles, where each byte contains 2 nibbles: [nibble 2 << 4 || nibble 1]
Unfortunately, sample rates for each samples are not defined within the .PDX file and have to be set manually with the appropriate command for that at play-time */
#define PDX_BANK_SIZE 96
#define PDX_SAMPLE_RATE 16000
typedef struct
{
unsigned int start_pointer;
unsigned short sample_length;
} PDX_HEADER;
#define UNUSED(x) (void)(x)
void DivEngine::loadPDX(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
PDX_HEADER headers[PDX_BANK_SIZE];
for(int i = 0; i < PDX_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned int)reader.readI_BE();
unsigned short empty = (unsigned short)reader.readS_BE(); //skip 1st 2 bytes
UNUSED(empty);
headers[i].sample_length = (unsigned short)reader.readS_BE();
}
for(int i = 0; i < PDX_BANK_SIZE; i++)
{
if(headers[i].start_pointer != 0 && headers[i].sample_length != 0)
{
DivSample* s = new DivSample;
s->rate = PDX_SAMPLE_RATE;
s->centerRate = PDX_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_VOX;
s->init(headers[i].sample_length * 2);
reader.seek((int)headers[i].start_pointer, SEEK_SET);
int sample_pos = 0;
for(unsigned short j = 0; j < headers[i].sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
curr_byte = (curr_byte << 4) | (curr_byte >> 4);
s->dataVOX[sample_pos] = curr_byte;
sample_pos++;
}
ret.push_back(s);
logI("pdx: start %d len %d", headers[i].start_pointer, headers[i].sample_length);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

142
src/engine/fileOps/ppc.cpp Normal file
View file

@ -0,0 +1,142 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PPC PMD's YM2608 ADPCM-B sample bank
/* ========================================
General
========================================
ADPCM RAM addresses: see docs/common.txt {
address_start = 0x0026
file_header_size = 0x0420
}
========================================
Header
========================================
0x0000 Identifier (30b)
"ADPCM DATA for PMD ver.4.4- "
0x001E Address of End of Data (32B blocks) in ADPCM RAM (1h)
File Size == address -> file offset
0x0020 - 0x041F 256 * {
Start of Sample (32b blocks) in ADPCM RAM (1h)
End of Sample (32b blocks) in ADPCM RAM (1h)
(0x0000 0x0000 -> no sample for this instrument ID)
}
========================================
Body
========================================
Stream of Sample Data {
Yamaha ADPCM-B encoding (4-Bit Signed ADPCM)
Mono
16kHz
(above sample rate according to KAJA's documentation
any sample rate possible, for different base note & octave)
} */
#define PPC_FILE_SIG "ADPCM DATA for PMD ver.4.4- "
#define PPC_BANK_SIZE 256
#define PPC_SAMPLE_RATE 16000
typedef struct
{
uint16_t start_pointer;
uint16_t end_pointer;
} PPC_HEADER;
#define UNUSED(x) (void)(x)
#define ADPCM_DATA_START 0x0420
void DivEngine::loadPPC(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
String file_sig = reader.readString(30);
unsigned short end_of_data = (unsigned short)reader.readS();
UNUSED(end_of_data);
if(file_sig != PPC_FILE_SIG) return;
PPC_HEADER headers[PPC_BANK_SIZE];
for(int i = 0; i < PPC_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned short)reader.readS();
headers[i].end_pointer = (unsigned short)reader.readS();
}
for(int i = 0; i < PPC_BANK_SIZE; i++)
{
if((headers[i].start_pointer != 0 || headers[i].end_pointer != 0) && headers[i].start_pointer < headers[i].end_pointer)
{
DivSample* s = new DivSample;
s->rate = PPC_SAMPLE_RATE;
s->centerRate = PPC_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_ADPCM_B;
s->init((headers[i].end_pointer - headers[i].start_pointer) * 32 * 2);
int sample_pos = 0;
int sample_length = (headers[i].end_pointer - headers[i].start_pointer) * 32;
//reader.seek(ADPCM_DATA_START + headers[i].start_pointer * 32, SEEK_SET);
for(int j = 0; j < sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
//curr_byte=(curr_byte<<4)|(curr_byte>>4);
s->dataB[sample_pos] = curr_byte;
sample_pos++;
}
logI("ppc: start %d end %d len in bytes %d", headers[i].start_pointer, headers[i].end_pointer, (headers[i].end_pointer - headers[i].start_pointer) * 32);
ret.push_back(s);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

125
src/engine/fileOps/pps.cpp Normal file
View file

@ -0,0 +1,125 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PPS AY-3-8910 sample bank
/* =======================================
Header
=======================================
0x0000 - 0x0053 14 * {
Pointer to Sample Data Start (1h)
Length of Sample Data (1h)
"Pitch"(?) (1b)
Volume Reduction (1b)
}
(0x0000 0x0000 [0x00 0x00] -> no sample for this instrument ID)
}
=======================================
Body
=======================================
Stream of Sample Data {
4-Bit Unsigned
(afaict)
Mono
16Hz
(based on tests, maybe alternatively 8kHz)
} */
#define PPS_BANK_SIZE 14
#define PPS_SAMPLE_RATE 16000
typedef struct
{
uint16_t start_pointer;
uint16_t sample_length;
uint8_t _pitch;
uint8_t _vol;
} PPS_HEADER;
void DivEngine::loadPPS(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
PPS_HEADER headers[PPS_BANK_SIZE];
for(int i = 0; i < PPS_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned short)reader.readS();
headers[i].sample_length = (unsigned short)reader.readS();
headers[i]._pitch = (unsigned char)reader.readC();
headers[i]._vol = (unsigned char)reader.readC();
}
for(int i = 0; i < PPS_BANK_SIZE; i++)
{
if(headers[i].start_pointer != 0 || headers[i].sample_length != 0
|| headers[i]._pitch != 0 || headers[i]._vol != 0)
{
DivSample* s = new DivSample;
s->rate = PPS_SAMPLE_RATE;
s->centerRate = PPS_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_8BIT;
s->init(headers[i].sample_length * 2); //byte per sample
reader.seek((int)headers[i].start_pointer, SEEK_SET);
int sample_pos = 0;
for(int j = 0; j < headers[i].sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
s->data8[sample_pos] = (curr_byte >> 4) | (curr_byte & 0xf0);
s->data8[sample_pos] += 0x80;
sample_pos++;
s->data8[sample_pos] = (curr_byte << 4) | (curr_byte & 0xf);
s->data8[sample_pos] += 0x80;
sample_pos++;
}
ret.push_back(s);
logI("pps: start %d len %d", headers[i].start_pointer, headers[i].sample_length);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

158
src/engine/fileOps/pvi.cpp Normal file
View file

@ -0,0 +1,158 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PVI YM2608 ADPCM-B sample bank
/* =======================================
General
=======================================
ADPCM RAM addresses: see docs/common.txt {
address_start = 0x0000
file_header_size = 0x0210
}
=======================================
Header
=======================================
0x0000 Identifier (4b)
"PVI2"
0x0004 - 0x0007 Unknown Settings (4b)
Unknown mappings PCME switches <-> Values
"0x10 0x00 0x10 0x02" in all example files
First 1h may be Start Address in ADPCM RAM?
0x0008 - 0x0009 "Delta-N" playback frequency (1h)
Default 0x49BA "== 16kHz"??
0x000A Unknown (1b)
RAM type? (Docs indicate values 0 or 8, all example files have value 0x02?)
0x000B Amount of defined Samples (1b)
0x000C - 0x000F Unknown (4b)
Padding?
"0x00 0x00 0x00 0x00" in all example files
0x0010 - 0x020F 128 * {
Start of Sample (32b blocks) in ADPCM RAM (1h)
End of Sample (32b blocks) in ADPCM RAM (1h)
(0x0000 0x0000 -> no sample for this instrument ID)
}
=======================================
Body
=======================================
Stream of Sample Data {
Yamaha ADPCM-B encoding (4-Bit Signed ADPCM)
Mono
Sample rate as specified earlier
(examples i have seems Stereo or 32kHz despite "16kHz" playback frequency setting?)
} */
#define PVIV2_FILE_SIG "PVI2"
#define PVIV1_FILE_SIG "PVI1"
#define PVI_BANK_SIZE 128
#define PVI_SAMPLE_RATE 16000
typedef struct
{
uint16_t start_pointer;
uint16_t end_pointer;
} PVI_HEADER;
#define UNUSED(x) (void)(x)
#define ADPCM_DATA_START 0x0210
void DivEngine::loadPVI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
String file_sig = reader.readString(4);
if(file_sig != PVIV1_FILE_SIG && file_sig != PVIV2_FILE_SIG) return;
unsigned int unknown_settings = (unsigned int)reader.readI();
UNUSED(unknown_settings);
unsigned short delta_n = (unsigned short)reader.readS();
UNUSED(delta_n);
unsigned char one_byte = (unsigned char)reader.readC();
UNUSED(one_byte);
unsigned char amount_of_samples = (unsigned char)reader.readC();
UNUSED(amount_of_samples);
unsigned int padding = (unsigned int)reader.readI();
UNUSED(padding);
PVI_HEADER headers[PVI_BANK_SIZE];
for(int i = 0; i < PVI_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned short)reader.readS();
headers[i].end_pointer = (unsigned short)reader.readS();
}
for(int i = 0; i < PVI_BANK_SIZE; i++)
{
if((headers[i].start_pointer != 0 || headers[i].end_pointer != 0) && headers[i].start_pointer < headers[i].end_pointer)
{
DivSample* s = new DivSample;
s->rate = PVI_SAMPLE_RATE;
s->centerRate = PVI_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_ADPCM_B;
s->init((headers[i].end_pointer - headers[i].start_pointer) * 32 * 2);
int sample_pos = 0;
int sample_length = (headers[i].end_pointer - headers[i].start_pointer) * 32;
reader.seek(ADPCM_DATA_START + headers[i].start_pointer * 32, SEEK_SET);
for(int j = 0; j < sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
//curr_byte=(curr_byte<<4)|(curr_byte>>4);
s->dataB[sample_pos] = curr_byte;
sample_pos++;
}
logI("pvi: start %d end %d len in bytes %d", headers[i].start_pointer, headers[i].end_pointer, (headers[i].end_pointer - headers[i].start_pointer) * 32);
ret.push_back(s);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

155
src/engine/fileOps/pzi.cpp Normal file
View file

@ -0,0 +1,155 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2024 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 "fileOpsCommon.h"
class DivEngine;
//PZI 8-bit PCM sample bank
/* =======================================
Header
=======================================
0x0000 Identifier (4b)
"PZI1"
0x0004 - 0x001F Unknown (28b)
Part of identifier? Settings?
All (\0)s in all example files
0x0020 - 0x091F 128 * {
Start of Sample after header (2h)
Length of Sample (2h)
Offset of loop start from sample start (2h)
Offset of loop end from sample start (2h)
Sample rate (1h)
(0xFFFFFFFF 0xFFFFFFFF loop offsets -> no loop information)
}
=======================================
Body
=======================================
Stream of Sample Data {
Unsigned 8-Bit
Mono
Sample rate as specified in header
} */
#define PZI_BANK_SIZE 128
#define PZI_FILE_SIG "PZI1"
#define NO_LOOP (0xFFFFFFFFU)
#define SAMPLE_DATA_OFFSET 0x0920
#define MAX_SANITY_CAP 9999999
#define HEADER_JUNK_SIZE 28
typedef struct
{
uint32_t start_pointer;
uint32_t sample_length;
uint32_t loop_start;
uint32_t loop_end;
uint16_t sample_rate;
} PZI_HEADER;
#define UNUSED(x) (void)(x)
void DivEngine::loadPZI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
{
try
{
reader.seek(0, SEEK_SET);
PZI_HEADER headers[PZI_BANK_SIZE];
String file_sig = reader.readString(4);
if(file_sig != PZI_FILE_SIG) return;
for (int i = 0; i < HEADER_JUNK_SIZE; i++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
UNUSED(curr_byte);
}
for(int i = 0; i < PZI_BANK_SIZE; i++)
{
headers[i].start_pointer = (unsigned int)reader.readI();
headers[i].sample_length = (unsigned int)reader.readI();
headers[i].loop_start = (unsigned int)reader.readI();
headers[i].loop_end = (unsigned int)reader.readI();
headers[i].sample_rate = (unsigned short)reader.readS();
}
for(int i = 0; i < PZI_BANK_SIZE; i++)
{
if (headers[i].start_pointer < MAX_SANITY_CAP && headers[i].sample_length < MAX_SANITY_CAP &&
headers[i].loop_start < MAX_SANITY_CAP && headers[i].loop_end < MAX_SANITY_CAP &&
headers[i].start_pointer > 0 && headers[i].sample_length > 0)
{
DivSample* s = new DivSample;
s->rate = headers[i].sample_rate;
s->centerRate = headers[i].sample_rate;
s->depth = DIV_SAMPLE_DEPTH_8BIT;
s->init(headers[i].sample_length); //byte per sample
reader.seek((int)headers[i].start_pointer + SAMPLE_DATA_OFFSET, SEEK_SET);
int sample_pos = 0;
for (uint32_t j = 0; j < headers[i].sample_length; j++)
{
unsigned char curr_byte = (unsigned char)reader.readC();
curr_byte += 0x80;
s->data8[sample_pos] = curr_byte;
sample_pos++;
}
if (headers[i].loop_start != NO_LOOP && headers[i].loop_end != NO_LOOP)
{
s->loop = true;
s->loopMode = DIV_SAMPLE_LOOP_FORWARD;
s->loopStart = headers[i].loop_start;
s->loopEnd = headers[i].loop_end;
}
ret.push_back(s);
logI("pzi: start %d len %d sample rate %d loop start %d loop end %d", headers[i].start_pointer, headers[i].sample_length,
headers[i].sample_rate, headers[i].loop_start, headers[i].loop_end);
}
}
}
catch (EndOfFileException& e)
{
lastError=_("premature end of file");
logE("premature end of file");
}
}

View file

@ -19,21 +19,6 @@
#include "fileOpsCommon.h" #include "fileOpsCommon.h"
// SBI and some other OPL containers
struct sbi_t {
uint8_t Mcharacteristics,
Ccharacteristics,
Mscaling_output,
Cscaling_output,
Meg_AD,
Ceg_AD,
Meg_SR,
Ceg_SR,
Mwave,
Cwave,
FeedConnect;
};
static void readSbiOpData(sbi_t& sbi, SafeReader& reader) { static void readSbiOpData(sbi_t& sbi, SafeReader& reader) {
sbi.Mcharacteristics = reader.readC(); sbi.Mcharacteristics = reader.readC();
sbi.Ccharacteristics = reader.readC(); sbi.Ccharacteristics = reader.readC();
@ -51,6 +36,7 @@ static void readSbiOpData(sbi_t& sbi, SafeReader& reader) {
bool DivEngine::loadS3M(unsigned char* file, size_t len) { bool DivEngine::loadS3M(unsigned char* file, size_t len) {
struct InvalidHeaderException {}; struct InvalidHeaderException {};
bool success=false; bool success=false;
bool opl2=!getConfInt("s3mOPL3",0);
char magic[4]={0,0,0,0}; char magic[4]={0,0,0,0};
SafeReader reader=SafeReader(file,len); SafeReader reader=SafeReader(file,len);
warnings=""; warnings="";
@ -273,11 +259,16 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
bool hasPCM=false; bool hasPCM=false;
bool hasFM=false; bool hasFM=false;
int numChans=0; int numChans=0;
int realNumChans=0;
for (int i=0; i<32; i++) { for (int ch=0; ch<32; ch++) {
if (chanSettings[i]==255) continue; if (chanSettings[ch]!=255) realNumChans++;
if ((chanSettings[i]&127)>=32) continue; }
if ((chanSettings[i]&127)>=16) {
for (int ch=0; ch<32; ch++) {
if (chanSettings[ch]==255) continue;
if ((chanSettings[ch]&127)>=32) continue;
if ((chanSettings[ch]&127)>=16) {
hasFM=true; hasFM=true;
} else { } else {
hasPCM=true; hasPCM=true;
@ -287,34 +278,69 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
if (hasFM && hasPCM) break; if (hasFM && hasPCM) break;
} }
int pcmChan=hasFM?9:0; int pcmChan=hasFM?(opl2 ? 9 : 18):0;
int fmChan=hasPCM?32:0; int fmChan=hasPCM?32:0;
int invalidChan=40; int invalidChan=40;
for (int i=0; i<32; i++) { for (int ch=0; ch<32; ch++) {
if (chanSettings[i]==255) { if (chanSettings[ch]==255) {
chanMap[i]=invalidChan++; chanMap[ch]=invalidChan++;
continue; continue;
} }
if ((chanSettings[i]&127)>=32) { if ((chanSettings[ch]&127)>=32) {
chanMap[i]=invalidChan++; chanMap[ch]=invalidChan++;
continue; continue;
} }
if ((chanSettings[i]&127)>=16) { if ((chanSettings[ch]&127)>=16) {
chanMap[i]=fmChan++; chanMap[ch]=fmChan++;
} else { } else {
chanMap[i]=pcmChan++; chanMap[ch]=pcmChan++;
} }
} }
char buffer[40];
int chanIndex = 1;
if (hasPCM) { if (hasPCM) {
for (int i=pcmChan; i<32; i++) { for(int ch = 0; ch < pcmChan - (realNumChans - (hasFM ? 9 : 0)); ch++)
ds.subsong[0]->chanShow[i]=false; {
ds.subsong[0]->chanShowChanOsc[i]=false; ds.subsong[0]->chanShow[ch]=false;
ds.subsong[0]->chanShowChanOsc[ch]=false;
}
for (int ch=pcmChan; ch<32; ch++) {
ds.subsong[0]->chanShow[ch]=false;
ds.subsong[0]->chanShowChanOsc[ch]=false;
}
for(int ch = 0; ch < 32; ch++)
{
if(ds.subsong[0]->chanShow[ch])
{
snprintf(buffer, 40, _("Channel %d"), chanIndex);
ds.subsong[0]->chanName[ch] = buffer;
chanIndex++;
}
}
}
if (hasFM && !opl2) {
for (int ch=(hasPCM?32:0) + 9; ch<(hasPCM?32:0) + 18; ch++) {
ds.subsong[0]->chanShow[ch]=false;
ds.subsong[0]->chanShowChanOsc[ch]=false;
}
chanIndex = 1;
for (int ch=(hasPCM?32:0); ch<(hasPCM?32:0) + 9; ch++) {
snprintf(buffer, 40, _("FM %d"), chanIndex);
ds.subsong[0]->chanName[ch] = buffer;
chanIndex++;
} }
} }
logV("numChans: %d",numChans); logV("numChans: %d",numChans);
logV("realNumChans: %d",realNumChans);
ds.systemName="PC"; ds.systemName="PC";
if (hasPCM) { if (hasPCM) {
@ -327,7 +353,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
ds.systemLen++; ds.systemLen++;
} }
if (hasFM) { if (hasFM) {
ds.system[ds.systemLen]=DIV_SYSTEM_OPL2; ds.system[ds.systemLen]=opl2 ? DIV_SYSTEM_OPL2 : DIV_SYSTEM_OPL3;
ds.systemVol[ds.systemLen]=1.0f; ds.systemVol[ds.systemLen]=1.0f;
ds.systemPan[ds.systemLen]=0; ds.systemPan[ds.systemLen]=0;
ds.systemLen++; ds.systemLen++;

View file

@ -24,10 +24,12 @@
#include "sfWrapper.h" #include "sfWrapper.h"
#endif #endif
DivSample* DivEngine::sampleFromFile(const char* path) { std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
std::vector<DivSample*> ret;
if (song.sample.size()>=256) { if (song.sample.size()>=256) {
lastError="too many samples!"; lastError="too many samples!";
return NULL; return ret;
} }
BUSY_BEGIN; BUSY_BEGIN;
warnings=""; warnings="";
@ -58,6 +60,110 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
} }
extS+=i; extS+=i;
} }
if(extS == ".pps" || extS == ".ppc" || extS == ".pvi" ||
extS == ".pdx" || extS == ".pzi" || extS == ".p86" ||
extS == ".p") //sample banks!
{
String stripPath;
const char* pathReduxEnd=strrchr(pathRedux,'.');
if (pathReduxEnd==NULL) {
stripPath=pathRedux;
} else {
for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) {
stripPath+=*i;
}
}
FILE* f=ps_fopen(path,"rb");
if (f==NULL) {
lastError=strerror(errno);
return ret;
}
unsigned char* buf;
ssize_t len;
if (fseek(f,0,SEEK_END)!=0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
len=ftell(f);
if (len<0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
if (len==(SIZE_MAX>>1)) {
lastError=strerror(errno);
fclose(f);
return ret;
}
if (len==0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
if (fseek(f,0,SEEK_SET)!=0) {
lastError=strerror(errno);
fclose(f);
return ret;
}
buf=new unsigned char[len];
if (fread(buf,1,len,f)!=(size_t)len) {
logW("did not read entire sample bank file buffer!");
lastError=_("did not read entire sample bank file!");
delete[] buf;
return ret;
}
fclose(f);
SafeReader reader = SafeReader(buf,len);
if(extS == ".pps")
{
loadPPS(reader,ret,stripPath);
}
if(extS == ".ppc")
{
loadPPC(reader,ret,stripPath);
}
if(extS == ".pvi")
{
loadPVI(reader,ret,stripPath);
}
if(extS == ".pdx")
{
loadPDX(reader,ret,stripPath);
}
if(extS == ".pzi")
{
loadPZI(reader,ret,stripPath);
}
if(extS == ".p86")
{
loadP86(reader,ret,stripPath);
}
if(extS == ".p")
{
loadP(reader,ret,stripPath);
}
if((int)ret.size() > 0)
{
int counter = 0;
for(DivSample* s: ret)
{
s->name = fmt::sprintf("%s sample %d", stripPath, counter);
counter++;
}
}
delete[] buf; //done with buffer
BUSY_END;
return ret;
}
if (extS==".dmc" || extS==".brr") { // read as .dmc or .brr if (extS==".dmc" || extS==".brr") { // read as .dmc or .brr
size_t len=0; size_t len=0;
DivSample* sample=new DivSample; DivSample* sample=new DivSample;
@ -68,7 +174,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
delete sample; delete sample;
return NULL; return ret;
} }
if (fseek(f,0,SEEK_END)<0) { if (fseek(f,0,SEEK_END)<0) {
@ -76,7 +182,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
delete sample; delete sample;
return NULL; return ret;
} }
len=ftell(f); len=ftell(f);
@ -86,7 +192,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError="file is empty!"; lastError="file is empty!";
delete sample; delete sample;
return NULL; return ret;
} }
if (len==(SIZE_MAX>>1)) { if (len==(SIZE_MAX>>1)) {
@ -94,7 +200,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError="file is invalid!"; lastError="file is invalid!";
delete sample; delete sample;
return NULL; return ret;
} }
if (fseek(f,0,SEEK_SET)<0) { if (fseek(f,0,SEEK_SET)<0) {
@ -102,7 +208,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
delete sample; delete sample;
return NULL; return ret;
} }
if (extS==".dmc") { if (extS==".dmc") {
@ -120,7 +226,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError="wait... is that right? no I don't think so..."; lastError="wait... is that right? no I don't think so...";
delete sample; delete sample;
return NULL; return ret;
} }
unsigned char* dataBuf=sample->dataDPCM; unsigned char* dataBuf=sample->dataDPCM;
@ -147,14 +253,14 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError="BRR sample is empty!"; lastError="BRR sample is empty!";
delete sample; delete sample;
return NULL; return ret;
} }
} else if ((len%9)!=0) { } else if ((len%9)!=0) {
fclose(f); fclose(f);
BUSY_END; BUSY_END;
lastError="possibly corrupt BRR sample!"; lastError="possibly corrupt BRR sample!";
delete sample; delete sample;
return NULL; return ret;
} }
} }
@ -163,16 +269,17 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
BUSY_END; BUSY_END;
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
delete sample; delete sample;
return NULL; return ret;
} }
BUSY_END; BUSY_END;
return sample; ret.push_back(sample);
return ret;
} }
} }
#ifndef HAVE_SNDFILE #ifndef HAVE_SNDFILE
lastError="Furnace was not compiled with libsndfile!"; lastError="Furnace was not compiled with libsndfile!";
return NULL; return ret;
#else #else
SF_INFO si; SF_INFO si;
SFWrapper sfWrap; SFWrapper sfWrap;
@ -186,13 +293,13 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
} else { } 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)); 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; return ret;
} }
if (si.frames>16777215) { if (si.frames>16777215) {
lastError="this sample is too big! max sample size is 16777215."; lastError="this sample is too big! max sample size is 16777215.";
sfWrap.doClose(); sfWrap.doClose();
BUSY_END; BUSY_END;
return NULL; return ret;
} }
void* buf=NULL; void* buf=NULL;
sf_count_t sampleLen=sizeof(short); sf_count_t sampleLen=sizeof(short);
@ -298,7 +405,8 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
if (sample->centerRate>64000) sample->centerRate=64000; if (sample->centerRate>64000) sample->centerRate=64000;
sfWrap.doClose(); sfWrap.doClose();
BUSY_END; BUSY_END;
return sample; ret.push_back(sample);
return ret;
#endif #endif
} }

View file

@ -27,7 +27,7 @@ uint8_t DivOPLAInterface::ymfm_external_read(ymfm::access_class type, uint32_t a
if (adpcmBMem==NULL) { if (adpcmBMem==NULL) {
return 0; return 0;
} }
return adpcmBMem[address&0xffffff]; return adpcmBMem[address&0x3ffff];
case ymfm::ACCESS_PCM: case ymfm::ACCESS_PCM:
if (pcmMem==NULL) { if (pcmMem==NULL) {
return 0; return 0;

View file

@ -1663,26 +1663,34 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
chan[i].panPos+=chan[i].panRate; chan[i].panPos+=chan[i].panRate;
chan[i].panPos&=255; chan[i].panPos&=255;
// calculate... // calculate inverted...
switch (chan[i].panPos&0xc0) { switch (chan[i].panPos&0xc0) {
case 0: // center -> right case 0: // center -> right
chan[i].panL=0xff-((chan[i].panPos&0x3f)<<2); chan[i].panL=((chan[i].panPos&0x3f)<<2);
chan[i].panR=0xff; chan[i].panR=0;
break; break;
case 0x40: // right -> center case 0x40: // right -> center
chan[i].panL=(chan[i].panPos&0x3f)<<2; chan[i].panL=0xff-((chan[i].panPos&0x3f)<<2);
chan[i].panR=0xff; chan[i].panR=0;
break; break;
case 0x80: // center -> left case 0x80: // center -> left
chan[i].panL=0xff; chan[i].panL=0;
chan[i].panR=0xff-((chan[i].panPos&0x3f)<<2); chan[i].panR=((chan[i].panPos&0x3f)<<2);
break; break;
case 0xc0: // left -> center case 0xc0: // left -> center
chan[i].panL=0xff; chan[i].panL=0;
chan[i].panR=(chan[i].panPos&0x3f)<<2; chan[i].panR=0xff-((chan[i].panPos&0x3f)<<2);
break; break;
} }
// multiply by depth
chan[i].panL=(chan[i].panL*chan[i].panDepth)/15;
chan[i].panR=(chan[i].panR*chan[i].panDepth)/15;
// then invert it to get final panning
chan[i].panL^=0xff;
chan[i].panR^=0xff;
dispatchCmd(DivCommand(DIV_CMD_PANNING,i,chan[i].panL,chan[i].panR)); dispatchCmd(DivCommand(DIV_CMD_PANNING,i,chan[i].panL,chan[i].panR));
} }

View file

@ -36,6 +36,8 @@ static float oscDebugMax=1.0;
static float oscDebugPower=1.0; static float oscDebugPower=1.0;
static int oscDebugRepeat=1; static int oscDebugRepeat=1;
static int numApples=1; static int numApples=1;
static int getGainChan=0;
static int getGainVol=0;
static void _drawOsc(const ImDrawList* drawList, const ImDrawCmd* cmd) { static void _drawOsc(const ImDrawList* drawList, const ImDrawCmd* cmd) {
if (cmd!=NULL) { if (cmd!=NULL) {
@ -721,6 +723,13 @@ void FurnaceGUI::drawDebug() {
ImGui::TreePop(); ImGui::TreePop();
} }
#endif #endif
if (ImGui::TreeNode("Get Gain Test")) {
float realVol=e->getGain(getGainChan,getGainVol);
ImGui::InputInt("Chan",&getGainChan);
ImGui::InputInt("Vol",&getGainVol);
ImGui::Text("result: %.0f%%",realVol*100.0f);
ImGui::TreePop();
}
if (ImGui::TreeNode("User Interface")) { if (ImGui::TreeNode("User Interface")) {
if (ImGui::Button("Inspect")) { if (ImGui::Button("Inspect")) {
inspectorOpen=!inspectorOpen; inspectorOpen=!inspectorOpen;

View file

@ -676,6 +676,17 @@ void FurnaceGUI::doAction(int what) {
latchTarget=0; latchTarget=0;
latchNibble=false; latchNibble=false;
break; break;
case GUI_ACTION_PAT_ABSORB_INSTRUMENT: {
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false);
if (!pat) break;
for (int i=cursor.y; i>=0; i--) {
if (pat->data[i][2] >= 0) {
curIns=pat->data[i][2];
break;
}
}
break;
}
case GUI_ACTION_INS_LIST_ADD: case GUI_ACTION_INS_LIST_ADD:
if (settings.insTypeMenu) { if (settings.insTypeMenu) {

View file

@ -3828,8 +3828,9 @@ bool FurnaceGUI::loop() {
} }
int sampleCountBefore=e->song.sampleLen; int sampleCountBefore=e->song.sampleLen;
std::vector<DivInstrument*> instruments=e->instrumentFromFile(ev.drop.file,true,settings.readInsNames); std::vector<DivInstrument*> instruments=e->instrumentFromFile(ev.drop.file,true,settings.readInsNames);
std::vector<DivSample*> samples = e->sampleFromFile(ev.drop.file);
DivWavetable* droppedWave=NULL; DivWavetable* droppedWave=NULL;
DivSample* droppedSample=NULL; //DivSample* droppedSample=NULL;
if (!instruments.empty()) { if (!instruments.empty()) {
if (e->song.sampleLen!=sampleCountBefore) { if (e->song.sampleLen!=sampleCountBefore) {
e->renderSamplesP(); e->renderSamplesP();
@ -3854,10 +3855,24 @@ bool FurnaceGUI::loop() {
} }
nextWindow=GUI_WINDOW_WAVE_LIST; nextWindow=GUI_WINDOW_WAVE_LIST;
MARK_MODIFIED; MARK_MODIFIED;
} else if ((droppedSample=e->sampleFromFile(ev.drop.file))!=NULL) { }
else if (!samples.empty())
{
if (e->song.sampleLen!=sampleCountBefore) {
//e->renderSamplesP();
}
if (!e->getWarnings().empty())
{
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
}
int sampleCount=-1; int sampleCount=-1;
sampleCount=e->addSamplePtr(droppedSample); for (DivSample* s: samples)
if (sampleCount>=0 && settings.selectAssetOnLoad) { {
sampleCount=e->addSamplePtr(s);
}
//sampleCount=e->addSamplePtr(droppedSample);
if (sampleCount>=0 && settings.selectAssetOnLoad)
{
curSample=sampleCount; curSample=sampleCount;
updateSampleTex=true; updateSampleTex=true;
} }
@ -5319,24 +5334,43 @@ bool FurnaceGUI::loop() {
String errs=_("there were some errors while loading samples:\n"); String errs=_("there were some errors while loading samples:\n");
bool warn=false; bool warn=false;
for (String i: fileDialog->getFileName()) { for (String i: fileDialog->getFileName()) {
DivSample* s=e->sampleFromFile(i.c_str()); std::vector<DivSample*> samples=e->sampleFromFile(i.c_str());
if (s==NULL) { if (samples.empty()) {
if (fileDialog->getFileName().size()>1) { if (fileDialog->getFileName().size()>1) {
warn=true; warn=true;
errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError());
} else {; } else {;
showError(e->getLastError()); showError(e->getLastError());
} }
} else { }
if (e->addSamplePtr(s)==-1) { else
if (fileDialog->getFileName().size()>1) { {
warn=true; if((int)samples.size() == 1)
errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); {
} else { if (e->addSamplePtr(samples[0]) == -1)
showError(e->getLastError()); {
if (fileDialog->getFileName().size()>1)
{
warn=true;
errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError());
}
else
{
showError(e->getLastError());
}
} }
} else { else
MARK_MODIFIED; {
MARK_MODIFIED;
}
}
else
{
for (DivSample* s: samples) { //ask which samples to load!
pendingSamples.push_back(std::make_pair(s,false));
}
displayPendingSamples=true;
replacePendingSample = false;
} }
} }
} }
@ -5345,24 +5379,44 @@ bool FurnaceGUI::loop() {
} }
break; break;
} }
case GUI_FILE_SAMPLE_OPEN_REPLACE: { case GUI_FILE_SAMPLE_OPEN_REPLACE:
DivSample* s=e->sampleFromFile(copyOfName.c_str()); {
if (s==NULL) { std::vector<DivSample*> samples=e->sampleFromFile(copyOfName.c_str());
if (samples.empty())
{
showError(e->getLastError()); showError(e->getLastError());
} else { }
if (curSample>=0 && curSample<(int)e->song.sample.size()) { else
e->lockEngine([this,s]() { {
// if it crashes here please tell me... if((int)samples.size() == 1)
DivSample* oldSample=e->song.sample[curSample]; {
e->song.sample[curSample]=s; if (curSample>=0 && curSample<(int)e->song.sample.size())
delete oldSample; {
e->renderSamples(); DivSample* s = samples[0];
MARK_MODIFIED; e->lockEngine([this, s]()
}); {
updateSampleTex=true; // if it crashes here please tell me...
} else { DivSample* oldSample=e->song.sample[curSample];
showError(_("...but you haven't selected a sample!")); e->song.sample[curSample]= s;
delete s; delete oldSample;
e->renderSamples();
MARK_MODIFIED;
});
updateSampleTex=true;
}
else
{
showError(_("...but you haven't selected a sample!"));
delete samples[0];
}
}
else
{
for (DivSample* s: samples) { //ask which samples to load!
pendingSamples.push_back(std::make_pair(s,false));
}
displayPendingSamples=true;
replacePendingSample = true;
} }
} }
break; break;
@ -5741,6 +5795,11 @@ bool FurnaceGUI::loop() {
ImGui::OpenPopup(_("Select Instrument")); ImGui::OpenPopup(_("Select Instrument"));
} }
if (displayPendingSamples) {
displayPendingSamples=false;
ImGui::OpenPopup(_("Select Sample"));
}
if (displayPendingRawSample) { if (displayPendingRawSample) {
displayPendingRawSample=false; displayPendingRawSample=false;
ImGui::OpenPopup(_("Import Raw Sample")); ImGui::OpenPopup(_("Import Raw Sample"));
@ -6569,6 +6628,191 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup(); ImGui::EndPopup();
} }
// TODO: fix style
centerNextWindow(_("Select Sample"),canvasW,canvasH);
if (ImGui::BeginPopupModal(_("Select Sample"),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
bool quitPlease=false;
ImGui::AlignTextToFramePadding();
ImGui::Text(_("this is a sample bank! select which ones to load:"));
ImGui::SameLine();
if (ImGui::Button(_("All"))) {
for (std::pair<DivSample*,bool>& i: pendingSamples) {
i.second=true;
}
}
ImGui::SameLine();
if (ImGui::Button(_("None"))) {
for (std::pair<DivSample*,bool>& i: pendingSamples) {
i.second=false;
}
}
bool reissueSearch=false;
bool anySelected=false;
float sizeY=ImGui::GetFrameHeightWithSpacing()*pendingSamples.size();
if (sizeY>(canvasH-180.0*dpiScale))
{
sizeY=canvasH-180.0*dpiScale;
if (sizeY<60.0*dpiScale) sizeY=60.0*dpiScale;
}
if (ImGui::BeginTable("PendingSamplesList",1,ImGuiTableFlags_ScrollY,ImVec2(0.0f,sizeY)))
{
if (sampleBankSearchQuery.empty())
{
for (size_t i=0; i<pendingSamples.size(); i++)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
String id=fmt::sprintf("%d: %s",(int)i,pendingSamples[i].first->name);
if (pendingInsSingle)
{
if (ImGui::Selectable(id.c_str()))
{
pendingSamples[i].second=true;
quitPlease=true;
}
}
else
{
// TODO:fixstyle from hereonwards
ImGuiIO& io = ImGui::GetIO();
if(ImGui::Checkbox(id.c_str(),&pendingSamples[i].second) && io.KeyShift)
{
for(int jj = (int)i - 1; jj >= 0; jj--)
{
if(pendingSamples[jj].second) //pressed shift and there's selected item above
{
for(int k = jj; k < (int)i; k++)
{
pendingSamples[k].second = true;
}
break;
}
}
}
}
if (pendingSamples[i].second) anySelected=true;
}
}
else //display search results
{
if(reissueSearch)
{
String lowerCase=sampleBankSearchQuery;
for (char& ii: lowerCase)
{
if (ii>='A' && ii<='Z') ii+='a'-'A';
}
sampleBankSearchResults.clear();
for (int j=0; j < (int)pendingSamples.size(); j++)
{
String lowerCase1 = pendingSamples[j].first->name;
for (char& ii: lowerCase1)
{
if (ii>='A' && ii<='Z') ii+='a'-'A';
}
if (lowerCase1.find(lowerCase)!=String::npos)
{
sampleBankSearchResults.push_back(std::make_pair(pendingSamples[j].first, pendingSamples[j].second));
}
}
}
for (size_t i=0; i<sampleBankSearchResults.size(); i++)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
String id=fmt::sprintf("%d: %s",(int)i,sampleBankSearchResults[i].first->name);
ImGuiIO& io = ImGui::GetIO();
if(ImGui::Checkbox(id.c_str(),&sampleBankSearchResults[i].second) && io.KeyShift)
{
for(int jj = (int)i - 1; jj >= 0; jj--)
{
if(sampleBankSearchResults[jj].second) //pressed shift and there's selected item above
{
for(int k = jj; k < (int)i; k++)
{
sampleBankSearchResults[k].second = true;
}
break;
}
}
}
if (sampleBankSearchResults[i].second) anySelected=true;
}
for (size_t i=0; i<pendingSamples.size(); i++)
{
if(sampleBankSearchResults.size() > 0)
{
for (size_t j=0; j<sampleBankSearchResults.size(); j++)
{
if(sampleBankSearchResults[j].first == pendingSamples[i].first && sampleBankSearchResults[j].second && pendingSamples[i].first != NULL)
{
pendingSamples[i].second = true;
if (pendingSamples[i].second) anySelected=true;
break;
}
}
}
}
}
ImGui::EndTable();
}
ImGui::BeginDisabled(!anySelected);
if (ImGui::Button(_("OK"))) {
quitPlease=true;
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button(_("Cancel")) || ImGui::IsKeyPressed(ImGuiKey_Escape)) {
for (std::pair<DivSample*,bool>& i: pendingSamples) {
i.second=false;
}
quitPlease=true;
}
if (quitPlease)
{
ImGui::CloseCurrentPopup();
int counter = 0;
for (std::pair<DivSample*,bool>& i: pendingSamples)
{
if (!i.second)
{
delete i.first;
}
else
{
if(counter == 0 && replacePendingSample)
{
*e->song.sample[curSample]=*i.first;
replacePendingSample = false;
}
else
{
e->addSamplePtr(i.first);
}
}
counter++;
}
curSample = (int)e->song.sample.size() - 1;
pendingSamples.clear();
}
ImGui::EndPopup();
}
centerNextWindow(_("Import Raw Sample"),canvasW,canvasH); centerNextWindow(_("Import Raw Sample"),canvasW,canvasH);
if (ImGui::BeginPopupModal(_("Import Raw Sample"),NULL,ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::BeginPopupModal(_("Import Raw Sample"),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text(_("Data type:")); ImGui::Text(_("Data type:"));
@ -7581,7 +7825,15 @@ bool FurnaceGUI::init() {
#endif #endif
compatFormats+="*.dmc "; compatFormats+="*.dmc ";
compatFormats+="*.brr"; compatFormats+="*.brr ";
compatFormats+="*.ppc ";
compatFormats+="*.pps ";
compatFormats+="*.pvi ";
compatFormats+="*.pdx ";
compatFormats+="*.pzi ";
compatFormats+="*.p86 ";
compatFormats+="*.p";
audioLoadFormats[1]=compatFormats; audioLoadFormats[1]=compatFormats;
audioLoadFormats.push_back(_("NES DPCM data")); audioLoadFormats.push_back(_("NES DPCM data"));
@ -7590,6 +7842,27 @@ bool FurnaceGUI::init() {
audioLoadFormats.push_back(_("SNES Bit Rate Reduction")); audioLoadFormats.push_back(_("SNES Bit Rate Reduction"));
audioLoadFormats.push_back("*.brr"); audioLoadFormats.push_back("*.brr");
audioLoadFormats.push_back(_("PMD YM2608 ADPCM-B sample bank"));
audioLoadFormats.push_back("*.ppc");
audioLoadFormats.push_back(_("PDR 4-bit AY-3-8910 sample bank"));
audioLoadFormats.push_back("*.pps");
audioLoadFormats.push_back(_("FMP YM2608 ADPCM-B sample bank"));
audioLoadFormats.push_back("*.pvi");
audioLoadFormats.push_back(_("MDX OKI ADPCM sample bank"));
audioLoadFormats.push_back("*.pdx");
audioLoadFormats.push_back(_("FMP 8-bit PCM sample bank"));
audioLoadFormats.push_back("*.pzi");
audioLoadFormats.push_back(_("PMD 8-bit PCM sample bank"));
audioLoadFormats.push_back("*.p86");
audioLoadFormats.push_back(_("PMD OKI ADPCM sample bank"));
audioLoadFormats.push_back("*.p");
audioLoadFormats.push_back(_("all files")); audioLoadFormats.push_back(_("all files"));
audioLoadFormats.push_back("*"); audioLoadFormats.push_back("*");
@ -8012,6 +8285,8 @@ FurnaceGUI::FurnaceGUI():
snesFilterHex(false), snesFilterHex(false),
modTableHex(false), modTableHex(false),
displayEditString(false), displayEditString(false),
displayPendingSamples(false),
replacePendingSample(false),
displayExportingROM(false), displayExportingROM(false),
changeCoarse(false), changeCoarse(false),
mobileEdit(false), mobileEdit(false),

View file

@ -816,6 +816,7 @@ enum FurnaceGUIActions {
GUI_ACTION_PAT_LATCH, GUI_ACTION_PAT_LATCH,
GUI_ACTION_PAT_SCROLL_MODE, GUI_ACTION_PAT_SCROLL_MODE,
GUI_ACTION_PAT_CLEAR_LATCH, GUI_ACTION_PAT_CLEAR_LATCH,
GUI_ACTION_PAT_ABSORB_INSTRUMENT,
GUI_ACTION_PAT_MAX, GUI_ACTION_PAT_MAX,
GUI_ACTION_INS_LIST_MIN, GUI_ACTION_INS_LIST_MIN,
@ -1591,7 +1592,7 @@ class FurnaceGUI {
int sampleTexW, sampleTexH; int sampleTexW, sampleTexH;
bool updateSampleTex; bool updateSampleTex;
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery, sampleBankSearchQuery;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport; String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport;
String workingDirVGMExport, workingDirZSMExport, workingDirROMExport; String workingDirVGMExport, workingDirZSMExport, workingDirROMExport;
String workingDirFont, workingDirColors, workingDirKeybinds; String workingDirFont, workingDirColors, workingDirKeybinds;
@ -1603,6 +1604,7 @@ class FurnaceGUI {
String folderString; String folderString;
std::vector<DivSystem> sysSearchResults; std::vector<DivSystem> sysSearchResults;
std::vector<std::pair<DivSample*,bool>> sampleBankSearchResults;
std::vector<FurnaceGUISysDef> newSongSearchResults; std::vector<FurnaceGUISysDef> newSongSearchResults;
std::vector<int> paletteSearchResults; std::vector<int> paletteSearchResults;
FixedQueue<String,32> recentFile; FixedQueue<String,32> recentFile;
@ -1618,6 +1620,7 @@ class FurnaceGUI {
bool displayNew, displayExport, displayPalette, fullScreen, preserveChanPos, sysDupCloneChannels, sysDupEnd, noteInputPoly, notifyWaveChange; bool displayNew, displayExport, displayPalette, fullScreen, preserveChanPos, sysDupCloneChannels, sysDupEnd, noteInputPoly, notifyWaveChange;
bool wantScrollListIns, wantScrollListWave, wantScrollListSample; bool wantScrollListIns, wantScrollListWave, wantScrollListSample;
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString; bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString;
bool displayPendingSamples, replacePendingSample;
bool displayExportingROM; bool displayExportingROM;
bool changeCoarse; bool changeCoarse;
bool mobileEdit; bool mobileEdit;
@ -1959,6 +1962,7 @@ class FurnaceGUI {
unsigned int maxUndoSteps; unsigned int maxUndoSteps;
float vibrationStrength; float vibrationStrength;
int vibrationLength; int vibrationLength;
int s3mOPL3;
String mainFontPath; String mainFontPath;
String headFontPath; String headFontPath;
String patFontPath; String patFontPath;
@ -2217,6 +2221,7 @@ class FurnaceGUI {
maxUndoSteps(100), maxUndoSteps(100),
vibrationStrength(0.5f), vibrationStrength(0.5f),
vibrationLength(20), vibrationLength(20),
s3mOPL3(1),
mainFontPath(""), mainFontPath(""),
headFontPath(""), headFontPath(""),
patFontPath(""), patFontPath(""),
@ -2373,6 +2378,7 @@ class FurnaceGUI {
std::vector<DivCommand> cmdStream; std::vector<DivCommand> cmdStream;
std::vector<Particle> particles; std::vector<Particle> particles;
std::vector<std::pair<DivInstrument*,bool>> pendingIns; std::vector<std::pair<DivInstrument*,bool>> pendingIns;
std::vector<std::pair<DivSample*,bool>> pendingSamples;
std::vector<FurnaceGUISysCategory> sysCategories; std::vector<FurnaceGUISysCategory> sysCategories;

View file

@ -687,6 +687,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("PAT_LATCH", _N("Set note input latch"), 0), D("PAT_LATCH", _N("Set note input latch"), 0),
D("PAT_SCROLL_MODE", _N("Change mobile scroll mode"), 0), D("PAT_SCROLL_MODE", _N("Change mobile scroll mode"), 0),
D("PAT_CLEAR_LATCH", _N("Clear note input latch"), 0), D("PAT_CLEAR_LATCH", _N("Clear note input latch"), 0),
D("PAT_ABSORB_INSTRUMENT", _N("Set current instrument to channel's current instrument column"), 0),
D("PAT_MAX", "", NOT_AN_ACTION), D("PAT_MAX", "", NOT_AN_ACTION),
D("INS_LIST_MIN", _N("---Instrument list"), NOT_AN_ACTION), D("INS_LIST_MIN", _N("---Instrument list"), NOT_AN_ACTION),

View file

@ -26,6 +26,8 @@
#include "imgui.h" #include "imgui.h"
#include "imgui_internal.h" #include "imgui_internal.h"
#include "../ta-utils.h"
struct FurnacePlotArrayGetterData struct FurnacePlotArrayGetterData
{ {
const float* Values; const float* Values;
@ -270,12 +272,13 @@ int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx),
float lineHeight=ImGui::GetTextLineHeight()/2.0; float lineHeight=ImGui::GetTextLineHeight()/2.0;
for (int i=0; i<bits && overlay_text[i]; i++) { for (int i=0; i<bits && overlay_text[i]; i++) {
ImGui::PushStyleColor(ImGuiCol_Text,ImVec4(0,0,0,1.0f)); ImGui::PushStyleColor(ImGuiCol_Text,ImVec4(0,0,0,1.0f));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight-1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits))); const char* text=_(overlay_text[i]);
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight+1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits))); ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight-1), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight-1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits))); ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight+1), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight+1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits))); ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight-1), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight+1), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::PopStyleColor(); ImGui::PopStyleColor();
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y-lineHeight), ImVec2(frame_bb.Max.x,frame_bb.Max.y+lineHeight), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits))); ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y-lineHeight), ImVec2(frame_bb.Max.x,frame_bb.Max.y+lineHeight), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
} }
} }

View file

@ -1215,28 +1215,63 @@ void FurnaceGUI::initSystemPresets() {
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "") CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
} }
); );
SUB_ENTRY(
"Sega TeraDrive", {
CH(DIV_SYSTEM_YM2612, 1.0f, 0, ""),
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
}
);
SUB_SUB_ENTRY(
"Sega TeraDrive (extended channel 3)", {
CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""),
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
}
);
SUB_SUB_ENTRY(
"Sega TeraDrive (CSM)", {
CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""),
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
}
);
SUB_SUB_ENTRY(
"Sega TeraDrive (DualPCM)", {
CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, ""),
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
}
);
SUB_SUB_ENTRY(
"Sega TeraDrive (DualPCM, extended channel 3)", {
CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, ""),
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
}
);
ENTRY( ENTRY(
"Sharp X1", { "Sharp X1", {
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3") CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3")
} }
); );
SUB_ENTRY( SUB_ENTRY(
"Sharp X1 + FM Addon", { "Sharp X1 + FM Addon", {
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3"), CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3"),
CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2") CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2")
} }
); );
ENTRY( ENTRY(
"Sharp X68000", { "Sharp X68000", {
CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2"), CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2"),
CH(DIV_SYSTEM_MSM6258, 1.0f, 0, "clockSel=2") CH(DIV_SYSTEM_MSM6258, 1.0f, 0, "clockSel=2")
} }
); );
ENTRY( ENTRY(
"FM-7", { "FM-7", {
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=12"), CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=12"),
CH(DIV_SYSTEM_YM2203, 1.0f, 0, "clockSel=5") CH(DIV_SYSTEM_YM2203, 1.0f, 0, "clockSel=5")
} }
); );
SUB_ENTRY( SUB_ENTRY(
"FM-7 (extended channel 3)", { "FM-7 (extended channel 3)", {

View file

@ -1259,6 +1259,14 @@ void FurnaceGUI::drawSettings() {
} }
popDestColor(); popDestColor();
// SUBSECTION IMPORT
CONFIG_SUBSECTION(_("Import"));
bool s3mOPL3B=settings.s3mOPL3;
if (ImGui::Checkbox(_("Use OPL3 instead of OPL2 for S3M import"),&s3mOPL3B)) {
settings.s3mOPL3=s3mOPL3B;
settingsChanged=true;
}
END_SECTION; END_SECTION;
} }
CONFIG_SECTION(_("Audio")) { CONFIG_SECTION(_("Audio")) {
@ -2431,6 +2439,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_SONG); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_SONG);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CLEAR_LATCH); UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CLEAR_LATCH);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_ABSORB_INSTRUMENT);
KEYBIND_CONFIG_END; KEYBIND_CONFIG_END;
ImGui::TreePop(); ImGui::TreePop();
@ -4761,6 +4770,8 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
settings.vibrationStrength=conf.getFloat("vibrationStrength",0.5f); settings.vibrationStrength=conf.getFloat("vibrationStrength",0.5f);
settings.vibrationLength=conf.getInt("vibrationLength",20); settings.vibrationLength=conf.getInt("vibrationLength",20);
settings.s3mOPL3=conf.getInt("s3mOPL3",1);
settings.backupEnable=conf.getInt("backupEnable",1); settings.backupEnable=conf.getInt("backupEnable",1);
settings.backupInterval=conf.getInt("backupInterval",30); settings.backupInterval=conf.getInt("backupInterval",30);
settings.backupMaxCopies=conf.getInt("backupMaxCopies",5); settings.backupMaxCopies=conf.getInt("backupMaxCopies",5);
@ -5277,6 +5288,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
clampSetting(settings.backupMaxCopies,1,100); clampSetting(settings.backupMaxCopies,1,100);
clampSetting(settings.autoFillSave,0,1); clampSetting(settings.autoFillSave,0,1);
clampSetting(settings.autoMacroStepSize,0,1); clampSetting(settings.autoMacroStepSize,0,1);
clampSetting(settings.s3mOPL3,0,1);
if (settings.exportLoops<0.0) settings.exportLoops=0.0; if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -5350,6 +5362,8 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
conf.set("vibrationStrength",settings.vibrationStrength); conf.set("vibrationStrength",settings.vibrationStrength);
conf.set("vibrationLength",settings.vibrationLength); conf.set("vibrationLength",settings.vibrationLength);
conf.set("s3mOPL3",settings.s3mOPL3);
conf.set("backupEnable",settings.backupEnable); conf.set("backupEnable",settings.backupEnable);
conf.set("backupInterval",settings.backupInterval); conf.set("backupInterval",settings.backupInterval);
conf.set("backupMaxCopies",settings.backupMaxCopies); conf.set("backupMaxCopies",settings.backupMaxCopies);