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

This commit is contained in:
cam900 2023-11-15 19:19:47 +09:00
commit f6849dfa50
19 changed files with 274 additions and 157 deletions

View file

@ -4,11 +4,8 @@
# THE REAL TO-DO LIST # THE REAL TO-DO LIST
- Amiga's Period Modulation not working
- Song is silent after playing an order after loop point - Song is silent after playing an order after loop point
- Select loaded instrument on open - rewrite because I want a setting... - Select loaded instrument on open - rewrite because I want a setting...
- re-engineer volume handling? Sound Unit cries at me
- finish status view
- finish auto-clone - finish auto-clone
once you have done all of this (maybe not the first one) and merged the two or so pending pull requests, release 0.6.1 once you have done all of this (maybe not the first one) and merged the two or so pending pull requests, release 0.6.1

View file

@ -21,7 +21,8 @@ the following keyboard shortcuts work while on a text field:
these work similar to text fields, but you may only input numbers. these work similar to text fields, but you may only input numbers.
they also usually have `+` and `-` buttons which allow you to increase/decrease the amount when clicked (and rapidly do so when click-holding). they also usually have `+` and `-` buttons which allow you to increase/decrease the value when clicked (and rapidly do so when holding).
additionally, Ctrl-clicking these buttons may increase/decrease the value by a coarse amount.
## sliders ## sliders
@ -29,7 +30,8 @@ sliders are used for controlling values in a quick manner by being dragged.
using the scroll wheel while holding Ctrl will change the slider's value by small amounts. using the scroll wheel while holding Ctrl will change the slider's value by small amounts.
right-clicking or Ctrl-clicking or a slider (Command-click on macOS) will turn it into a number input field for a short period of time, allowing you to input precise values. right-clicking or Ctrl-clicking or a slider (Command-click on macOS) will turn it into a number input field, allowing you to input precise values.
once you click away it will become a slider again.
## windows ## windows
@ -37,13 +39,13 @@ right-clicking or Ctrl-clicking or a slider (Command-click on macOS) will turn i
windows may be moved, collapsed, closed or even docked around the workspace. windows may be moved, collapsed, closed or even docked around the workspace.
to move a window, press and hold the mouse button while on title bar or any empty space on it. to move a window, press and hold the left mouse button while on the title bar or any empty space on it.
then drag your mouse, and release it to stop moving. then drag your mouse, and release it to stop moving.
to resize a window, drag the bottom right corner (marked by a triangular tab) or the borders. to resize a window, drag the bottom right corner (marked by a triangular tab) or the borders.
to collapse a window, click on the triangle in the title bar. to collapse a window, click on the triangle in the title bar.
clicking again expands it. clicking again expands the window.
to close a window, click on the `X` at the top right corner, or select it from the "window" menu. to close a window, click on the `X` at the top right corner, or select it from the "window" menu.

View file

@ -6,6 +6,28 @@ in this very computer music trackers were born...
imported MOD files use this chip, and will set A-4 tuning to 436. imported MOD files use this chip, and will set A-4 tuning to 436.
## amplitude/period modulation
Amiga has support for (rather primitive) amplitude and period (frequency) modulation.
however, nobody has used this feature as it is rather useless, not well-documented and works in a complicated way.
Amiga sample playback is done by two chips: Paula (the one that you probably know) and Agnus (the one that actually feeds Paula with samples).
Agnus has several DMA (direct memory access) units which read from chip memory independent of the CPU. four of these DMA units are used for samples.
when DMA is enabled, Paula requests sample data from Agnus, and then plays these samples back.
there's a catch though. since the data bus is 16-bit, Paula requests **two** 8-bit samples at once! this explains why:
- the sample length registers are in words rather than bytes (thereby allowing samples up to 131070 in length)
- the maximum playback rate (31250Hz PAL; ~31469Hz NTSC) is two times the HBlank rate (Agnus fetches samples on HBlank, around 15625Hz on PAL or ~15734Hz on NTSC)
during normal sample playback, the first sample is output and then the second. afterwards, two more samples are fetched, and so on.
now, when amplitude or period modulation are enabled, things work differently.
the channel is silenced, and the two 8-bit samples are **treated as a big-endian 16-bit number**, which is then written to the next channel's volume or period.
in the case of amplitude modulation, only the second sample is significant because the volume register uses 7 bits (to represent 0 to 64 (65 to 127 are treated as 64)) and the other bits are ignored.
in the case of period modulation, both samples are significant. the first sample is the upper byte, and the second is the lower byte.
## effects ## effects
- `10xx`: **toggle low-pass filter.** `0` turns it off and `1` turns it on. - `10xx`: **toggle low-pass filter.** `0` turns it off and `1` turns it on.

View file

@ -7,6 +7,13 @@ this chip features:
- stereo soft panning - stereo soft panning
- accepts either 8-bit PCM or proprietary 8-bit µ-law compressed PCM samples - accepts either 8-bit PCM or proprietary 8-bit µ-law compressed PCM samples
## sample memory notice
this chip is rather unique when it comes to sample memory. be sure to read this notice.
the channels are in groups of four. a sample bank (128KB) may be selected for each group.
if a sample that is on a different bank plays in a group, the group is switched to that bank, and other channels will be silenced.
## effects ## effects
- `11xx`: **set noise mode.** - `11xx`: **set noise mode.**

View file

@ -18,6 +18,7 @@ as listed in the "Window" menu:
- [piano](piano.md) - [piano](piano.md)
- [oscilloscope](osc.md) - [oscilloscope](osc.md)
- [oscilloscope (X-Y)](xyosc.md)
- [oscilloscopes (per-channel)](chanosc.md) - [oscilloscopes (per-channel)](chanosc.md)
- [clock](clock.md) - [clock](clock.md)
- [register view](regview.md) - [register view](regview.md)

View file

@ -5,8 +5,8 @@ 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 view. pattern data will be kept. - **Visible**: 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. - crossed-arrows button: click and drag to rearrange pattern data throughout the song.
- note: this does _not_ move channels around within a chip! it only affects pattern data. - note: this does **not** move channels around! it only moves the channel's pattern data.
- **Name** is the name displayed at the top of each channel in the tracker view. - **Name**: the name displayed at the top of each channel in the pattern view.
- to the right of that is the abbreviation used above each channel in the order view. - the next setting is "short name", which is displayed in the orders view and/or when a channel is collapsed.

View file

@ -1,25 +1,24 @@
# oscilloscope (per-channel) # oscilloscope (per-channel)
the "Oscilloscope (per-channel)" dialog shows an individual oscilloscope for each channel during playback. the "Oscilloscope (per-channel)" windows displays several oscilloscope views (one per channel).
![oscilloscope per-channel configuration view](chanosc.png) ![oscilloscope per-channel configuration view](chanosc.png)
right-clicking within the view will change it to the configuration view shown above: right-clicking the view will display the configuration view shown above:
- **Columns**: arranges oscilloscopes into this many columns. - **Columns**: sets the amount of columns for the oscilloscope views.
- **Size (ms)**: sets what length of audio is visible in each oscilloscope. - **Size (ms)**: sets how much of a channel's output is captured for the oscilloscope view.
- **Center waveform**: does its best to latch the waveform to the channel's note frequency and centers the display. - **Center waveform**: when enabled, the displayed waveforms will be centered using an auto-correlation algorithm.
- **Automatic columns**: sets the number of columns based on the number of channels. - **Automatic columns**: sets the number of columns based on the number of channels.
- **Off**: use the Columns setting. - **Off**: use the Columns setting.
- **Mode 1**: always fewer columns than rows. - **Mode 1**: always fewer columns than rows.
- **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 oscilloscopes. - **Amplitude**: scales amplitude for all oscilloscope views.
- **Gradient**: see below. - **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.
- the color selector sets the color for all waveforms. right-clicking on it pops up an option dialog: - 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.
- **Alpha bar**: adds a transparency selector. - **Alpha bar**: display an opacity bar.
- the boxes below that are for selecting colors numerically by red-green-blue-alpha, hue-saturation-value-alpha, and HTML-style RGBA in hex. - **Text format**: this allows you to display some text on each oscilloscope view. the following codes may be used:
- **Text format**: this string determins what text is shown in the top-left of each oscilloscope. it can be any text, and the following shortcodes will be replaced with information about the channel:
- `%c`: channel name - `%c`: channel name
- `%C`: channel short name - `%C`: channel short name
- `%d`: channel number (starting from 0) - `%d`: channel number (starting from 0)
@ -35,27 +34,32 @@ right-clicking within the view will change it to the configuration view shown ab
- `%V`: volume (percentage) - `%V`: volume (percentage)
- `%b`: volume (hex) - `%b`: volume (hex)
- `%%`: percent sign - `%%`: percent sign
- the OK button returns from options view to the oscilloscopes.
click on OK to return to the main view.
## gradient ## gradient
![oscilloscope per-channel gradient configuration view](chanosc-gradient.png) ![oscilloscope per-channel gradient configuration view](chanosc-gradient.png)
in this mode, the color selector is replaced by a square field onto which circular "stops" can be placed. each stop adds a soft circle of color. the resulting image is used to look up the oscilloscope color as determined by each axis. when enabling the Gradient setting, a gradient view is displayed in where circular "points" can be placed.
each point adds a color spot.
the resulting image is used to look up the waveform's color as determined by each axis.
- right-click to place a stop. - right-click to place a point.
- left-click on a stop to change its color. the color selector is the same as above, with two additions: - left-click on a point to change its color:
- a color picker is displayed, alongside two settings.
- **Distance**: the size of the circle. - **Distance**: the size of the circle.
- **Spread**: the size of the solid center of the circle. increasing it fills more of the circle with the target color. - **Spread**: the size of the solid center of the circle. increasing it fills more of the circle with the color.
- middle-click on a point to delete it.
- **Background**: sets background color for entire field. - **Background**: sets the gradient's background color.
- **X Axis**: determines what the horizontal maps to, from left to right. - **X Axis**: determines what the horizontal axis maps to.
- **Y Axis**: determines what the vertical maps to. from bottom to top. these can be set to the following: - **Y Axis**: determines what the vertical axis maps to. these can be set to the following:
- **None (0%)**: stays at the left or bottom. - **None (0%)**: always left or bottom
- **None (50%)**: stays at the center. - **None (50%)**: always center
- **None (100%)**: stays at the right or top. - **None (100%)**: always right or top
- **Frequency**: changes color with note frequency. - **Frequency**: changes color with frequency
- **Volume**: changes color with volume. - **Volume**: changes color with volume
- **Channel**: changes color based on channel number. - **Channel**: changes color based on channel number (first channel is 0% and last is 100%)
- **Brightness**: currently does nothing. - **Brightness**: currently does nothing
- **Note Trigger**: changes color when a new note is played. - **Note Trigger**: changes color when a new note is played

View file

@ -1,15 +1,15 @@
# chip manager # chip manager
the **chip manager** window does exactly what it says. the **chip manager** window allows you to manage chips, including adding, changing, moving or removing them.
![chip manager](chip-manager.png) ![chip manager](chip-manager.png)
**Preserve channel order**: make existing pattern data stay in place even when chips are rearranged. if turned off, pattern data will rearrange to match (the default, and usually the desired behavior). **Preserve channel order**: make existing pattern data stay in place even when chips are rearranged. when turned off, pattern data will be arranged to match (the default, and usually desired behavior).
to move a chip around, click and drag the ![crossed-arrows](chip-manager-move.png) button to its left. to move a chip around, click and drag the ![crossed-arrows](chip-manager-move.png) button to the left.
to replace a chip with a different one, click the **Change** button and select the replacement. to replace a chip with a different one, click the **Change** button. this will display the chip selector.
to remove a chip entirely, click the ![X](chip-manager-remove.png) button. to remove a chip, click the ![X](chip-manager-remove.png) button.
click a chip's name to open its options, where one can set clock rate, chip variant, and other specifics. click on a chip's name to open chip configuration. this allows you to change chip options, such as clock rate, chip type and so on.

View file

@ -4,4 +4,4 @@
comments, credits, or any arbitrary text may be entered here. it has no effect on the song. comments, credits, or any arbitrary text may be entered here. it has no effect on the song.
there is no word wrap; long lines must be broken manually with the Enter key. there is no word wrap; long lines must be broken manually with the Enter/Return key.

View file

@ -1,5 +1,5 @@
# compatibility flags # compatibility flags
the **Compatibility Flags** window contains several tabs full of settings that change aspects of tracking and playback. a new Furnace song will have these disabled, while opening a DefleMask module, .mod, or earlier Furnace file will automatically set the appropriate options. the **Compatibility Flags** window contains several tabs full of settings that change playback behavior. a new Furnace song will have these disabled, while opening a DefleMask module, .mod, or earlier Furnace file will automatically set the appropriate options.
hovering over most options will bring up additional info about them. it is not recommended to change any of these, especially the ones in the DefleMask and Old Furnace sections. hovering over most options will bring up additional info about them. it is not recommended to change any of these, especially the ones in the DefleMask and Old Furnace sections.

View file

@ -1,9 +1,6 @@
# grooves # grooves
grooves are macros for speed. a **groove** is the equivalent of repeating `0Fxx` effects on each row to get a cycle of speeds. for example, a groove of "6 4 5 3" makes the first row 6 ticks long, the next row 4 ticks, then 5, 3, 6, 4, 5, 3...
a **groove** is the equivalent of repeating `0Fxx` commands on each row to get a cycle of speeds. for example, a groove of "6 4 5 3" makes the first row 6 ticks long, the next row 4 ticks, then 5, 3, 6, 4, 5, 3...
![groove](groove.png) ![groove](groove.png)
@ -13,18 +10,17 @@ to set the song's groove:
- click again so it becomes "Groove". - click again so it becomes "Groove".
- enter a sequence of up to 16 speeds. - enter a sequence of up to 16 speeds.
![groove patterns](grooves.png) ![groove patterns](grooves.png)
the "Grooves" window is for entering preset groove patterns. the "Grooves" window displays the list of groove patterns in the song.
- the **`+`** button adds a new groove pattern; click in the pattern to edit it. - the **`+`** button adds a new groove pattern; click in the groove pattern to edit it.
- the **`×`** buttons remove them. - the **`×`** buttons remove them.
a single `09xx` command will switch to the matching numbered groove pattern. a single `09xx` command will switch to the matching numbered groove pattern.
## BPM ## tempo
this is a non-exhaustive list of grooves and their equivalent BPM. this is a non-exhaustive list of grooves and their equivalent tempo in BPM.
note: this table assumes a song's tick rate setting is left at its default value for the chosen engine speed: 60 for NTSC, or 50 for PAL. note: this table assumes a song's tick rate setting is left at its default value for the chosen engine speed: 60 for NTSC, or 50 for PAL.

View file

@ -1,9 +1,9 @@
# oscilloscope # oscilloscope
the Oscilloscope shows the waveform of the mix of all currently playing sounds. the Oscilloscope shows the audio output as a waveform.
![oscilloscope view](osc.png) ![oscilloscope view](osc.png)
right-clicking on the oscilloscope toggles the adjustment sliders: right-clicking on the oscilloscope toggles adjustment sliders:
- waveform height zoom - waveform height (zoom)
- width of viewed audio (window size) in milliseconds. - window size (how much of the output to display) in milliseconds.

19
doc/8-advanced/xyosc.md Normal file
View file

@ -0,0 +1,19 @@
# oscilloscope (X-Y)
also called "vectorscope", this is similar to the normal oscilloscope in that it draws audio output as a waveform, but instead of being one-dimensional and going from left to right, it is plotted in two dimensions (usually X represents left output and Y represents right output).
(TODO: image)
right-clicking the X-Y oscilloscope window displays settings:
- **X Channel**: the output channel which will affect the X (horizontal) axis. default is 1 (left).
- **Invert**: flips the axis, therefore flipping the view horizontally.
- **Y Channel**: the output channel which will affect the Y (vertical) axis. default is 2 (right).
- **Invert**: flips the axis, therefore flipping the view vertically.
- **Zoom**: changes amplitude, making the plot bigger or smaller.
- **Samples**: the maximum number of samples to use for plotting.
- this setting may be replaced by another or removed in a future version.
- **Decay Time (ms)**: sets how long it takes for the points/lines to fade away.
- **Intensity**: changes the brightness of the dots/lines.
- **Line Thickness**: sets how thick lines are.
click **OK** to return to the oscilloscope view.

30
src/divasm/divasm.h Normal file
View file

@ -0,0 +1,30 @@
struct DivASMResult {
int line, err;
DivASMResult():
line(-1),
err(0) {}
};
struct DivASMFile {
String name;
SafeReader* data;
};
enum DivASMTarget {
DIV_ASM_TARGET_DUMMY=0,
DIV_ASM_TARGET_6502,
DIV_ASM_TARGET_SPC700
};
class DivASM {
std::vector<DivASMFile> files;
SafeWriter* result;
public:
DivASMResult getError();
SafeWriter* assemble(String name);
void addFile(String name, SafeReader* data);
DivASM(DivASMTarget target);
~DivASM();
};

View file

@ -149,7 +149,7 @@ std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
} else if (waveNum<65536) { } else if (waveNum<65536) {
seq->writeC((i<<4)|4); seq->writeC((i<<4)|4);
seq->writeS_BE(waveNum); seq->writeS_BE(waveNum);
} else{ } else {
seq->writeC((i<<4)|1); seq->writeC((i<<4)|1);
seq->writeC(waves[waveNum].pos>>16); seq->writeC(waves[waveNum].pos>>16);
seq->writeC(waves[waveNum].pos>>8); seq->writeC(waves[waveNum].pos>>8);

View file

@ -81,7 +81,18 @@ const char** DivPlatformAmiga::getRegisterSheet() {
void DivPlatformAmiga::acquire(short** buf, size_t len) { void DivPlatformAmiga::acquire(short** buf, size_t len) {
thread_local int outL, outR, output; thread_local int outL, outR, output;
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
if (--delay<0) delay=0;
if (!writes.empty() && delay<=0) {
QueuedWrite w=writes.front();
if (w.addr==0x96 && !(w.val&0x8000)) delay=4096/AMIGA_DIVIDER;
amiga.write(w.addr,w.val);
writes.pop();
}
bool hsync=bypassLimits; bool hsync=bypassLimits;
outL=0; outL=0;
outR=0; outR=0;
@ -99,7 +110,8 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
} }
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
// run DMA // run DMA
if (amiga.dmaEn && amiga.audEn[i] && !amiga.audIr[i]) { if (amiga.audEn[i]) amiga.mustDMA[i]=true;
if (amiga.dmaEn && amiga.mustDMA[i] && !amiga.audIr[i]) {
amiga.audTick[i]-=AMIGA_DIVIDER; amiga.audTick[i]-=AMIGA_DIVIDER;
if (amiga.audTick[i]<0) { if (amiga.audTick[i]<0) {
amiga.audTick[i]+=MAX(AMIGA_DIVIDER,amiga.audPer[i]); amiga.audTick[i]+=MAX(AMIGA_DIVIDER,amiga.audPer[i]);
@ -114,6 +126,8 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
amiga.audWord[i]=!amiga.audWord[i]; amiga.audWord[i]=!amiga.audWord[i];
} }
amiga.mustDMA[i]=amiga.audEn[i];
amiga.audByte[i]=!amiga.audByte[i]; amiga.audByte[i]=!amiga.audByte[i];
if (!amiga.audByte[i] && (amiga.useV[i] || amiga.useP[i])) { if (!amiga.audByte[i] && (amiga.useV[i] || amiga.useP[i])) {
amiga.nextOut2[i]=((unsigned char)amiga.audDat[0][i])<<8|((unsigned char)amiga.audDat[1][i]); amiga.nextOut2[i]=((unsigned char)amiga.audDat[0][i])<<8|((unsigned char)amiga.audDat[1][i]);
@ -130,7 +144,7 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
amiga.audPer[i+1]=amiga.nextOut2[i]; amiga.audPer[i+1]=amiga.nextOut2[i];
} }
} }
} else { } else if (!amiga.useV[i] && !amiga.useP[i]) {
amiga.nextOut[i]=amiga.audDat[amiga.audByte[i]][i]; amiga.nextOut[i]=amiga.audDat[amiga.audByte[i]][i];
} }
} }
@ -154,12 +168,12 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
// output // output
if (!isMuted[i]) { if (!isMuted[i]) {
if (amiga.audVol[i]>=64) { if ((amiga.audVol[i]&127)>=64) {
output=amiga.nextOut[i]<<6; output=amiga.nextOut[i]<<6;
} else if (amiga.audVol[i]<=0) { } else if ((amiga.audVol[i]&127)==0) {
output=0; output=0;
} else { } else {
output=amiga.nextOut[i]*volTable[amiga.audVol[i]][amiga.volPos]; output=amiga.nextOut[i]*volTable[amiga.audVol[i]&63][amiga.volPos];
} }
if (i==0 || i==3) { if (i==0 || i==3) {
outL+=(output*sep1)>>7; outL+=(output*sep1)>>7;
@ -168,7 +182,7 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
outL+=(output*sep2)>>7; outL+=(output*sep2)>>7;
outR+=(output*sep1)>>7; outR+=(output*sep1)>>7;
} }
oscBuf[i]->data[oscBuf[i]->needle++]=(amiga.nextOut[i]*MIN(64,amiga.audVol[i]))<<1; oscBuf[i]->data[oscBuf[i]->needle++]=(amiga.nextOut[i]*MIN(64,amiga.audVol[i]&127))<<1;
} else { } else {
oscBuf[i]->data[oscBuf[i]->needle++]=0; oscBuf[i]->data[oscBuf[i]->needle++]=0;
} }
@ -190,11 +204,6 @@ void DivPlatformAmiga::irq(int ch) {
if (chan[ch].irLocL==0x400 && chan[ch].irLocH==0 && chan[ch].irLen==1) { if (chan[ch].irLocL==0x400 && chan[ch].irLocH==0 && chan[ch].irLen==1) {
// turn off DMA // turn off DMA
rWrite(0x96,1<<ch); rWrite(0x96,1<<ch);
} else {
// write latched loc/len
chWrite(ch,0,chan[ch].irLocH);
chWrite(ch,2,chan[ch].irLocL);
chWrite(ch,4,chan[ch].irLen);
} }
// acknowledge interrupt // acknowledge interrupt
@ -202,111 +211,96 @@ void DivPlatformAmiga::irq(int ch) {
} }
#define UPDATE_DMA(x) \ #define UPDATE_DMA(x) \
amiga.dmaLen[x]=amiga.audLen[x]; \ dmaLen[x]=audLen[x]; \
amiga.dmaLoc[x]=amiga.audLoc[x]; \ dmaLoc[x]=audLoc[x]; \
amiga.audByte[x]=true; \ audByte[x]=true; \
amiga.audTick[x]=0; audTick[x]=0;
void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) { void DivPlatformAmiga::Amiga::write(unsigned short addr, unsigned short val) {
if (addr&1) return; if (addr&1) return;
//logV("%.3x = %.4x",addr,val);
regPool[addr>>1]=val;
if (!skipRegisterWrites && dumpWrites) {
addWrite(addr,val);
}
switch (addr&0x1fe) { switch (addr&0x1fe) {
case 0x96: { // DMACON case 0x96: { // DMACON
if (val&32768) { if (val&32768) {
if (val&1) amiga.audEn[0]=true; if (val&1) audEn[0]=true;
if (val&2) amiga.audEn[1]=true; if (val&2) audEn[1]=true;
if (val&4) amiga.audEn[2]=true; if (val&4) audEn[2]=true;
if (val&8) amiga.audEn[3]=true; if (val&8) audEn[3]=true;
if (val&512) amiga.dmaEn=true; if (val&512) dmaEn=true;
} else { } else {
if (val&1) { if (val&1) {
amiga.audEn[0]=false; audEn[0]=false;
UPDATE_DMA(0);
} }
if (val&2) { if (val&2) {
amiga.audEn[1]=false; audEn[1]=false;
UPDATE_DMA(1);
} }
if (val&4) { if (val&4) {
amiga.audEn[2]=false; audEn[2]=false;
UPDATE_DMA(2);
} }
if (val&8) { if (val&8) {
amiga.audEn[3]=false; audEn[3]=false;
UPDATE_DMA(3);
} }
if (val&512) { if (val&512) {
amiga.dmaEn=false; dmaEn=false;
} }
} }
break; break;
} }
case 0x9a: { // INTENA case 0x9a: { // INTENA
if (val&32768) { if (val&32768) {
if (val&128) amiga.audInt[0]=true; if (val&128) audInt[0]=true;
if (val&256) amiga.audInt[1]=true; if (val&256) audInt[1]=true;
if (val&512) amiga.audInt[2]=true; if (val&512) audInt[2]=true;
if (val&1024) amiga.audInt[3]=true; if (val&1024) audInt[3]=true;
} else { } else {
if (val&128) amiga.audInt[0]=false; if (val&128) audInt[0]=false;
if (val&256) amiga.audInt[1]=false; if (val&256) audInt[1]=false;
if (val&512) amiga.audInt[2]=false; if (val&512) audInt[2]=false;
if (val&1024) amiga.audInt[3]=false; if (val&1024) audInt[3]=false;
} }
break; break;
} }
case 0x9c: { // INTREQ case 0x9c: { // INTREQ
if (val&32768) { if (val&32768) {
if (val&128) { if (val&128) {
amiga.audIr[0]=true; audIr[0]=true;
irq(0);
} }
if (val&256) { if (val&256) {
amiga.audIr[1]=true; audIr[1]=true;
irq(1);
} }
if (val&512) { if (val&512) {
amiga.audIr[2]=true; audIr[2]=true;
irq(2);
} }
if (val&1024) { if (val&1024) {
amiga.audIr[3]=true; audIr[3]=true;
irq(3);
} }
} else { } else {
if (val&128) amiga.audIr[0]=false; if (val&128) audIr[0]=false;
if (val&256) amiga.audIr[1]=false; if (val&256) audIr[1]=false;
if (val&512) amiga.audIr[2]=false; if (val&512) audIr[2]=false;
if (val&1024) amiga.audIr[3]=false; if (val&1024) audIr[3]=false;
} }
break; break;
} }
case 0x9e: { // ADKCON case 0x9e: { // ADKCON
if (val&32768) { if (val&32768) {
if (val&1) amiga.useV[0]=true; if (val&1) useV[0]=true;
if (val&2) amiga.useV[1]=true; if (val&2) useV[1]=true;
if (val&4) amiga.useV[2]=true; if (val&4) useV[2]=true;
if (val&8) amiga.useV[3]=true; if (val&8) useV[3]=true;
if (val&16) amiga.useP[0]=true; if (val&16) useP[0]=true;
if (val&32) amiga.useP[1]=true; if (val&32) useP[1]=true;
if (val&64) amiga.useP[2]=true; if (val&64) useP[2]=true;
if (val&128) amiga.useP[3]=true; if (val&128) useP[3]=true;
} else { } else {
if (val&1) amiga.useV[0]=false; if (val&1) useV[0]=false;
if (val&2) amiga.useV[1]=false; if (val&2) useV[1]=false;
if (val&4) amiga.useV[2]=false; if (val&4) useV[2]=false;
if (val&8) amiga.useV[3]=false; if (val&8) useV[3]=false;
if (val&16) amiga.useP[0]=false; if (val&16) useP[0]=false;
if (val&32) amiga.useP[1]=false; if (val&32) useP[1]=false;
if (val&64) amiga.useP[2]=false; if (val&64) useP[2]=false;
if (val&128) amiga.useP[3]=false; if (val&128) useP[3]=false;
} }
break; break;
} }
@ -316,31 +310,31 @@ void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) {
bool updateDMA=false; bool updateDMA=false;
switch (addr&15) { switch (addr&15) {
case 0: // LCH case 0: // LCH
amiga.audLoc[ch]&=0xffff; audLoc[ch]&=0xffff;
amiga.audLoc[ch]|=val<<16; audLoc[ch]|=val<<16;
updateDMA=true; updateDMA=true;
break; break;
case 2: // LCL case 2: // LCL
amiga.audLoc[ch]&=0xffff0000; audLoc[ch]&=0xffff0000;
amiga.audLoc[ch]|=val&0xfffe; audLoc[ch]|=val&0xfffe;
updateDMA=true; updateDMA=true;
break; break;
case 4: // LEN case 4: // LEN
amiga.audLen[ch]=val; audLen[ch]=val;
updateDMA=true; updateDMA=true;
break; break;
case 6: // PER case 6: // PER
amiga.audPer[ch]=val; audPer[ch]=val;
break; break;
case 8: // VOL case 8: // VOL
amiga.audVol[ch]=val; audVol[ch]=val;
break; break;
case 10: // DAT case 10: // DAT
amiga.audDat[0][ch]=val&0xff; audDat[0][ch]=val&0xff;
amiga.audDat[1][ch]=val>>8; audDat[1][ch]=val>>8;
break; break;
} }
if (updateDMA && !amiga.audEn[ch]) { if (updateDMA && !mustDMA[ch]) {
UPDATE_DMA(ch); UPDATE_DMA(ch);
} }
} }
@ -349,6 +343,20 @@ void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) {
} }
} }
void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) {
if (addr&1) return;
//logV("%.3x = %.4x",addr,val);
if (!skipRegisterWrites) {
writes.push(QueuedWrite(addr,val));
regPool[addr>>1]=val;
if (dumpWrites) {
addWrite(addr,val);
}
}
}
void DivPlatformAmiga::updateWave(int ch) { void DivPlatformAmiga::updateWave(int ch) {
for (int i=0; i<MIN(256,(chan[ch].audLen<<1)); i++) { for (int i=0; i<MIN(256,(chan[ch].audLen<<1)); i++) {
sampleMem[(ch<<8)|i]=chan[ch].ws.output[i]^0x80; sampleMem[(ch<<8)|i]=chan[ch].ws.output[i]^0x80;
@ -417,6 +425,13 @@ void DivPlatformAmiga::tick(bool sysTick) {
if (dmaOff) rWrite(0x96,dmaOff); if (dmaOff) rWrite(0x96,dmaOff);
for (int i=0; i<4; i++) {
if (chan[i].updateWave) {
chan[i].updateWave=false;
updateWave(i);
}
}
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
double off=1.0; double off=1.0;
if (!chan[i].useWave && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) { if (!chan[i].useWave && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
@ -509,9 +524,16 @@ void DivPlatformAmiga::tick(bool sysTick) {
if (dmaOn) rWrite(0x96,0x8000|dmaOn); if (dmaOn) rWrite(0x96,0x8000|dmaOn);
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
if ((dmaOn&(1<<i)) && !chan[i].useWave && dumpWrites) { if ((dmaOn&(1<<i)) && !chan[i].useWave) {
// write latched loc/len
if (dumpWrites) {
addWrite(0x200+i,(chan[i].irLocH<<16)|chan[i].irLocL); addWrite(0x200+i,(chan[i].irLocH<<16)|chan[i].irLocL);
addWrite(0x204+i,chan[i].irLen); addWrite(0x204+i,chan[i].irLen);
} else {
chWrite(i,0,chan[i].irLocH);
chWrite(i,2,chan[i].irLocL);
chWrite(i,4,chan[i].irLen);
}
} }
} }
@ -520,10 +542,6 @@ void DivPlatformAmiga::tick(bool sysTick) {
chan[i].writeVol=false; chan[i].writeVol=false;
chWrite(i,8,chan[i].outVol); chWrite(i,8,chan[i].outVol);
} }
if (chan[i].updateWave) {
chan[i].updateWave=false;
updateWave(i);
}
} }
if (updateADKCon) { if (updateADKCon) {
@ -720,6 +738,7 @@ void DivPlatformAmiga::forceIns() {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
chan[i].insChanged=true; chan[i].insChanged=true;
chan[i].freqChanged=true; chan[i].freqChanged=true;
chan[i].writeVol=true;
/*chan[i].keyOn=false; /*chan[i].keyOn=false;
chan[i].keyOff=false; chan[i].keyOff=false;
chan[i].sample=-1;*/ chan[i].sample=-1;*/
@ -738,6 +757,7 @@ DivDispatchOscBuffer* DivPlatformAmiga::getOscBuffer(int ch) {
} }
void DivPlatformAmiga::reset() { void DivPlatformAmiga::reset() {
writes.clear();
memset(regPool,0,256*sizeof(unsigned short)); memset(regPool,0,256*sizeof(unsigned short));
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
chan[i]=DivPlatformAmiga::Channel(); chan[i]=DivPlatformAmiga::Channel();
@ -750,6 +770,7 @@ void DivPlatformAmiga::reset() {
filterOn=false; filterOn=false;
filtConst=filterOn?filtConstOn:filtConstOff; filtConst=filterOn?filtConstOn:filtConstOff;
updateADKCon=true; updateADKCon=true;
delay=0;
amiga=Amiga(); amiga=Amiga();
// enable DMA // enable DMA

View file

@ -21,6 +21,7 @@
#define _AMIGA_H #define _AMIGA_H
#include "../dispatch.h" #include "../dispatch.h"
#include "../../fixedQueue.h"
#include "../waveSynth.h" #include "../waveSynth.h"
class DivPlatformAmiga: public DivDispatch { class DivPlatformAmiga: public DivDispatch {
@ -59,12 +60,14 @@ class DivPlatformAmiga: public DivDispatch {
bool amigaModel; bool amigaModel;
bool filterOn; bool filterOn;
bool updateADKCon; bool updateADKCon;
short delay;
struct Amiga { struct Amiga {
// register state // register state
bool audInt[4]; // interrupt on bool audInt[4]; // interrupt on
bool audIr[4]; // interrupt request bool audIr[4]; // interrupt request
bool audEn[4]; // audio DMA on bool audEn[4]; // audio DMA on
bool mustDMA[4]; // audio DMA must run
bool useP[4]; // period modulation bool useP[4]; // period modulation
bool useV[4]; // volume modulation bool useV[4]; // volume modulation
@ -91,6 +94,8 @@ class DivPlatformAmiga: public DivDispatch {
unsigned short hPos; // horizontal position of beam unsigned short hPos; // horizontal position of beam
unsigned char state[4]; // current channel state unsigned char state[4]; // current channel state
void write(unsigned short addr, unsigned short val);
Amiga() { Amiga() {
memset(this,0,sizeof(*this)); memset(this,0,sizeof(*this));
} }
@ -113,6 +118,14 @@ class DivPlatformAmiga: public DivDispatch {
int sep1, sep2; int sep1, sep2;
struct QueuedWrite {
unsigned short addr;
unsigned short val;
QueuedWrite(): addr(0), val(9) {}
QueuedWrite(unsigned short a, unsigned short v): addr(a), val(v) {}
};
FixedQueue<QueuedWrite,512> writes;
friend void putDispatchChip(void*,int); friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int); friend void putDispatchChan(void*,int,int);
friend class DivExportAmigaValidation; friend class DivExportAmigaValidation;

View file

@ -103,6 +103,8 @@ void FurnaceGUI::doFind() {
int lastRow=e->curSubSong->patLen-1; int lastRow=e->curSubSong->patLen-1;
if (curQueryRangeY==1) { if (curQueryRangeY==1) {
finishSelection();
firstRow=selStart.y; firstRow=selStart.y;
lastRow=selEnd.y; lastRow=selEnd.y;
} }

View file

@ -2325,6 +2325,15 @@ void FurnaceGUI::drawSettings() {
} }
ImGui::Unindent(); ImGui::Unindent();
// SUBSECTION ASSETS
CONFIG_SUBSECTION("Assets");
bool insTypeMenuB=settings.insTypeMenu;
if (ImGui::Checkbox("Display instrument type menu when adding instrument",&insTypeMenuB)) {
settings.insTypeMenu=insTypeMenuB;
settingsChanged=true;
}
END_SECTION; END_SECTION;
} }
CONFIG_SECTION("Appearance") { CONFIG_SECTION("Appearance") {
@ -2888,12 +2897,6 @@ void FurnaceGUI::drawSettings() {
settingsChanged=true; settingsChanged=true;
} }
bool insTypeMenuB=settings.insTypeMenu;
if (ImGui::Checkbox("Display instrument type menu when adding instrument",&insTypeMenuB)) {
settings.insTypeMenu=insTypeMenuB;
settingsChanged=true;
}
// SUBSECTION MACRO EDITOR // SUBSECTION MACRO EDITOR
CONFIG_SUBSECTION("Macro Editor"); CONFIG_SUBSECTION("Macro Editor");
ImGui::Text("Macro editor layout:"); ImGui::Text("Macro editor layout:");