ref: 7e106b7505fbd4682ba11b7854c92f15dc5ea44e
parent: 02664d922f41be9050be714cedb677bc010ae903
author: Olav Sørensen <olav.sorensen@live.no>
date: Fri Sep 18 16:38:57 EDT 2020
Pushed v1.23 code - PAT2SMP now has higher quality sound. Especially noticable in LO mode. - PAT2SMP LO mode now uses 20864Hz (E-3 finetune 0) instead of 22168Hz (F-3 finetune +1) for slightly more room, and still has better quality than before because of the improvement above! - The optional 2x downsample during sample load is now of better quality. Or it should be, at least... - Fixed some bugs in the "sampling" screen (SAMPLER screen -> Sample) - The sampling feature now takes up less CPU time while downsampling. Also fixed a small error in the sinc resampling interpolation. - Don't block GUI input and don't show red mouse pointer for a while every time you cancel a requester. No idea why PT did this in the first place, what a workflow limiter! - Bugfix: Don't reset playback time counter on "play pattern" - protracker.ini: "A500LOWPASSFILTER" has been replaced with "FILTERMODEL" (A1200 or A500). Removed NORMALIZESAMPLING and SAMPLELOWPASS (deprecated). - Some other minor changes not worth of a mention - Code cleanup
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,5 @@
*.db-wal
*.db-shm
pt_pal_editor/vs2019_project/pt_pal_editor/Debug/pt_pal_editor.vcxproj.FileListAbsolute.txt
+vs2019_project/pt2-clone/Release/pt2-clone-win64.exe.recipe
+*.recipe
--- a/release/help.txt
+++ b/release/help.txt
@@ -11,6 +11,9 @@
* Is there a way to run the program in fullscreen mode?
- Press F11. On some keyboards/configurations you may need to press fn+F11.
+ * How can I toggle the audio type between Amiga 500 and Amiga 1200?
+ - Press F12. You can also change "FILTERMODEL" in protracker.ini.
+
* Alt+F5/ALT+F5 (copy/paste) doesn't work!
- Windows: you need to make sure those keybinding are disabled in
'GeForce Experience' if you have that software installed.
@@ -170,10 +173,10 @@
- Quality mode: -
HI: 28604Hz - Play the new sample at note A-3 (finetune +4)
- LO: 22168Hz - Play the new sample at note F-3 (finetune +1)
+ LO: 20864Hz - Play the new sample at note E-3 (finetune 0)
HI has the highest possible quality, but can only fit 2.29 seconds of audio.
- LO is worse in quality, but can fit slightly more at 2.95 seconds.
+ LO is worse in quality, but can fit slightly more at 3.14 seconds.
You should use this feature in sequences to fit as much rows as you need.
F.ex. start from row 0 to row 23, then make another sample from row 24 to 47,
--- a/release/macos/protracker.ini
+++ b/release/macos/protracker.ini
@@ -220,39 +220,16 @@
;
SAMPLINGFREQ=44100
-; Normalize sampled audio before converting to 8-bit
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: This one is for the audio sampling feature in the SAMPLER
-; screen. If it's set to TRUE, it will normalize the gain before it
-; converts the sample to 8-bit in the end. This will preserve as much
-; amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+; Syntax: A500 or A1200
+; Default value: A1200
+; Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+; A1200 has sharper sound but more aliasing, while A500 has more
+; filtered (muddy) sound but less aliasing. The filter model can
+; also be toggled by pressing F12 in the program.
;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
-; Audio buffer size
-; Syntax: Number, in samples
-; Default value: 1024
-; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-; isn't necessarily the actual value the audio API decides to use.
-; Lower means less audio latency but possible audio issues, higher
-; means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-; Syntax: TRUE or FALSE
-; Default value: FALSE
-; Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-; filter present in almost all Amigas. This will make the sound a bit
-; muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-; can also be toggled in the tracker by pressing F12. This must not be
-; confused with the "LED" filter which can be toggled with the pattern
-; command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
; Stereo separation
; Syntax: 0 to 100 (percent)
; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
;
STEREOSEPARATION=20
-
-; Low-pass samples before getting 2x downsampled during loading (if requested)
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: Set to false if you want slightly sharper sound when loading
-; samples that are 2x downsampled (if requested).
-; Keep in mind that you might get more aliasing in the sound if you
-; disable this, and certain sounds with a lot of treble might sound
-; weird (f.ex. hi-hats and cymbals). If the sample you load have a
-; frequency below 22kHz, it will never be downsampled (and thus this
-; setting changes nothing). I recommend keeping this set to TRUE.
+; Audio buffer size
+; Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+; isn't necessarily the actual value the audio API decides to use.
+; Lower means less audio latency but possible audio issues, higher
+; means more audio latency but less chance for issues.
;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
; End of config file
\ No newline at end of file
--- a/release/other/protracker.ini
+++ b/release/other/protracker.ini
@@ -220,39 +220,16 @@
;
SAMPLINGFREQ=44100
-; Normalize sampled audio before converting to 8-bit
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: This one is for the audio sampling feature in the SAMPLER
-; screen. If it's set to TRUE, it will normalize the gain before it
-; converts the sample to 8-bit in the end. This will preserve as much
-; amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+; Syntax: A500 or A1200
+; Default value: A1200
+; Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+; A1200 has sharper sound but more aliasing, while A500 has more
+; filtered (muddy) sound but less aliasing. The filter model can
+; also be toggled by pressing F12 in the program.
;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
-; Audio buffer size
-; Syntax: Number, in samples
-; Default value: 1024
-; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-; isn't necessarily the actual value the audio API decides to use.
-; Lower means less audio latency but possible audio issues, higher
-; means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-; Syntax: TRUE or FALSE
-; Default value: FALSE
-; Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-; filter present in almost all Amigas. This will make the sound a bit
-; muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-; can also be toggled in the tracker by pressing F12. This must not be
-; confused with the "LED" filter which can be toggled with the pattern
-; command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
; Stereo separation
; Syntax: 0 to 100 (percent)
; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
;
STEREOSEPARATION=20
-
-; Low-pass samples before getting 2x downsampled during loading (if requested)
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: Set to false if you want slightly sharper sound when loading
-; samples that are 2x downsampled (if requested).
-; Keep in mind that you might get more aliasing in the sound if you
-; disable this, and certain sounds with a lot of treble might sound
-; weird (f.ex. hi-hats and cymbals). If the sample you load have a
-; frequency below 22kHz, it will never be downsampled (and thus this
-; setting changes nothing). I recommend keeping this set to TRUE.
+; Audio buffer size
+; Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+; isn't necessarily the actual value the audio API decides to use.
+; Lower means less audio latency but possible audio issues, higher
+; means more audio latency but less chance for issues.
;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
; End of config file
\ No newline at end of file
--- a/release/win32/protracker.ini
+++ b/release/win32/protracker.ini
@@ -220,39 +220,16 @@
;
SAMPLINGFREQ=44100
-; Normalize sampled audio before converting to 8-bit
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: This one is for the audio sampling feature in the SAMPLER
-; screen. If it's set to TRUE, it will normalize the gain before it
-; converts the sample to 8-bit in the end. This will preserve as much
-; amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+; Syntax: A500 or A1200
+; Default value: A1200
+; Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+; A1200 has sharper sound but more aliasing, while A500 has more
+; filtered (muddy) sound but less aliasing. The filter model can
+; also be toggled by pressing F12 in the program.
;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
-; Audio buffer size
-; Syntax: Number, in samples
-; Default value: 1024
-; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-; isn't necessarily the actual value the audio API decides to use.
-; Lower means less audio latency but possible audio issues, higher
-; means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-; Syntax: TRUE or FALSE
-; Default value: FALSE
-; Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-; filter present in almost all Amigas. This will make the sound a bit
-; muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-; can also be toggled in the tracker by pressing F12. This must not be
-; confused with the "LED" filter which can be toggled with the pattern
-; command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
; Stereo separation
; Syntax: 0 to 100 (percent)
; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
;
STEREOSEPARATION=20
-
-; Low-pass samples before getting 2x downsampled during loading (if requested)
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: Set to false if you want slightly sharper sound when loading
-; samples that are 2x downsampled (if requested).
-; Keep in mind that you might get more aliasing in the sound if you
-; disable this, and certain sounds with a lot of treble might sound
-; weird (f.ex. hi-hats and cymbals). If the sample you load have a
-; frequency below 22kHz, it will never be downsampled (and thus this
-; setting changes nothing). I recommend keeping this set to TRUE.
+; Audio buffer size
+; Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+; isn't necessarily the actual value the audio API decides to use.
+; Lower means less audio latency but possible audio issues, higher
+; means more audio latency but less chance for issues.
;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
; End of config file
\ No newline at end of file
--- a/release/win64/protracker.ini
+++ b/release/win64/protracker.ini
@@ -220,39 +220,16 @@
;
SAMPLINGFREQ=44100
-; Normalize sampled audio before converting to 8-bit
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: This one is for the audio sampling feature in the SAMPLER
-; screen. If it's set to TRUE, it will normalize the gain before it
-; converts the sample to 8-bit in the end. This will preserve as much
-; amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+; Syntax: A500 or A1200
+; Default value: A1200
+; Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+; A1200 has sharper sound but more aliasing, while A500 has more
+; filtered (muddy) sound but less aliasing. The filter model can
+; also be toggled by pressing F12 in the program.
;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
-; Audio buffer size
-; Syntax: Number, in samples
-; Default value: 1024
-; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-; isn't necessarily the actual value the audio API decides to use.
-; Lower means less audio latency but possible audio issues, higher
-; means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-; Syntax: TRUE or FALSE
-; Default value: FALSE
-; Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-; filter present in almost all Amigas. This will make the sound a bit
-; muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-; can also be toggled in the tracker by pressing F12. This must not be
-; confused with the "LED" filter which can be toggled with the pattern
-; command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
; Stereo separation
; Syntax: 0 to 100 (percent)
; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
;
STEREOSEPARATION=20
-
-; Low-pass samples before getting 2x downsampled during loading (if requested)
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: Set to false if you want slightly sharper sound when loading
-; samples that are 2x downsampled (if requested).
-; Keep in mind that you might get more aliasing in the sound if you
-; disable this, and certain sounds with a lot of treble might sound
-; weird (f.ex. hi-hats and cymbals). If the sample you load have a
-; frequency below 22kHz, it will never be downsampled (and thus this
-; setting changes nothing). I recommend keeping this set to TRUE.
+; Audio buffer size
+; Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+; isn't necessarily the actual value the audio API decides to use.
+; Lower means less audio latency but possible audio issues, higher
+; means more audio latency but less chance for issues.
;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
; End of config file
\ No newline at end of file
--- a/src/pt2_audio.c
+++ b/src/pt2_audio.c
@@ -15,7 +15,7 @@
#else
#include <unistd.h>
#endif
-#include <math.h> // sqrt(),tan(),M_PI
+#include <math.h> // sqrt(),tan()
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -26,7 +26,6 @@
#include "pt2_blep.h"
#include "pt2_config.h"
#include "pt2_tables.h"
-#include "pt2_palette.h"
#include "pt2_textout.h"
#include "pt2_visuals.h"
#include "pt2_scopes.h"
@@ -34,19 +33,15 @@
#include "pt2_pat2smp.h"
#include "pt2_sync.h"
#include "pt2_structs.h"
+#include "pt2_rcfilter.h"
+#include "pt2_ledfilter.h"
#define INITIAL_DITHER_SEED 0x12345000
-typedef struct ledFilter_t
-{
- double buffer[4];
- double c, ci, feedback, bg, cg, c2;
-} ledFilter_t;
-
-static volatile int8_t filterFlags;
+static volatile bool ledFilterEnabled;
+static volatile uint8_t filterModel;
static int8_t defStereoSep;
static bool amigaPanFlag;
-static uint16_t ch1Pan, ch2Pan, ch3Pan, ch4Pan;
static int32_t oldPeriod = -1, randSeed = INITIAL_DITHER_SEED;
static uint32_t audLatencyPerfValInt, audLatencyPerfValFrac;
static uint64_t tickTime64, tickTime64Frac;
@@ -53,7 +48,7 @@
static double *dMixBufferL, *dMixBufferR, *dMixBufferLUnaligned, *dMixBufferRUnaligned, dOldVoiceDelta, dOldVoiceDeltaMul;
static double dPrngStateL, dPrngStateR;
static blep_t blep[AMIGA_VOICES], blepVol[AMIGA_VOICES];
-static rcFilter_t filterLoA500, filterLoA1200, filterHi;
+static rcFilter_t filterLoA500, filterLoA1200, filterHiA500, filterHiA1200;
static ledFilter_t filterLED;
static SDL_AudioDeviceID dev;
@@ -66,52 +61,6 @@
bool intMusic(void); // defined in pt_modplayer.c
-static void calcAudioLatencyVars(int32_t audioBufferSize, int32_t audioFreq)
-{
- double dInt, dFrac;
-
- if (audioFreq == 0)
- return;
-
- const double dAudioLatencySecs = audioBufferSize / (double)audioFreq;
-
- dFrac = modf(dAudioLatencySecs * editor.dPerfFreq, &dInt);
-
- // integer part
- audLatencyPerfValInt = (int32_t)dInt;
-
- // fractional part (scaled to 0..2^32-1)
- dFrac *= UINT32_MAX+1.0;
- audLatencyPerfValFrac = (uint32_t)dFrac;
-}
-
-void setSyncTickTimeLen(uint32_t timeLen, uint32_t timeLenFrac)
-{
- tickTimeLen = timeLen;
- tickTimeLenFrac = timeLenFrac;
-}
-
-static void generateBpmTables(void)
-{
- for (int32_t i = 32; i <= 255; i++)
- {
- const double dBpmHz = i / 2.5;
-
- audio.bpmTab[i-32] = audio.outputRate / dBpmHz;
- audio.bpmTab28kHz[i-32] = PAT2SMP_HI_FREQ / dBpmHz; // PAT2SMP hi quality
- audio.bpmTab22kHz[i-32] = PAT2SMP_LO_FREQ / dBpmHz; // PAT2SMP low quality
- audio.bpmTabMod2Wav[i-32] = MOD2WAV_FREQ / dBpmHz; // MOD2WAV
- }
-}
-
-static void clearLEDFilterState(void)
-{
- filterLED.buffer[0] = 0.0; // left channel
- filterLED.buffer[1] = 0.0;
- filterLED.buffer[2] = 0.0; // right channel
- filterLED.buffer[3] = 0.0;
-}
-
void setLEDFilter(bool state, bool doLockAudio)
{
const bool audioWasntLocked = !audio.locked;
@@ -118,16 +67,10 @@
if (doLockAudio && audioWasntLocked)
lockAudio();
+ clearLEDFilterState(&filterLED);
+
editor.useLEDFilter = state;
- if (editor.useLEDFilter)
- {
- clearLEDFilterState();
- filterFlags |= FILTER_LED_ENABLED;
- }
- else
- {
- filterFlags &= ~FILTER_LED_ENABLED;
- }
+ ledFilterEnabled = editor.useLEDFilter;
if (doLockAudio && audioWasntLocked)
unlockAudio();
@@ -139,161 +82,53 @@
if (audioWasntLocked)
lockAudio();
+ clearLEDFilterState(&filterLED);
+
editor.useLEDFilter ^= 1;
- if (editor.useLEDFilter)
- {
- clearLEDFilterState();
- filterFlags |= FILTER_LED_ENABLED;
- }
- else
- {
- filterFlags &= ~FILTER_LED_ENABLED;
- }
+ ledFilterEnabled = editor.useLEDFilter;
if (audioWasntLocked)
unlockAudio();
}
-/* Imperfect "LED" filter implementation. This may be further improved in the future.
-** Based upon ideas posted by mystran @ the kvraudio.com forum.
-**
-** This filter may not function correctly used outside the fixed-cutoff context here!
-*/
-
-static double sigmoid(double x, double coefficient)
+static void calcAudioLatencyVars(int32_t audioBufferSize, int32_t audioFreq)
{
- /* Coefficient from:
- ** 0.0 to inf (linear)
- ** -1.0 to -inf (linear)
- */
- return x / (x + coefficient) * (coefficient + 1.0);
-}
+ double dInt, dFrac;
-static void calcLEDFilterCoeffs(const double sr, const double hz, const double fb, ledFilter_t *filter)
-{
- /* tan() may produce NaN or other bad results in some cases!
- ** It appears to work correctly with these specific coefficients.
- */
- const double c = (hz < (sr / 2.0)) ? tan((M_PI * hz) / sr) : 1.0;
- const double g = 1.0 / (1.0 + c);
+ if (audioFreq == 0)
+ return;
- // dirty compensation
- const double s = 0.5;
- const double t = 0.5;
- const double ic = c > t ? 1.0 / ((1.0 - s*t) + s*c) : 1.0;
- const double cg = c * g;
- const double fbg = 1.0 / (1.0 + fb * cg*cg);
+ const double dAudioLatencySecs = audioBufferSize / (double)audioFreq;
- filter->c = c;
- filter->ci = g;
- filter->feedback = 2.0 * sigmoid(fb, 0.5);
- filter->bg = fbg * filter->feedback * ic;
- filter->cg = cg;
- filter->c2 = c * 2.0;
-}
+ dFrac = modf(dAudioLatencySecs * editor.dPerfFreq, &dInt);
-static inline void LEDFilter(ledFilter_t *f, const double *in, double *out)
-{
- const double in_1 = DENORMAL_OFFSET;
- const double in_2 = DENORMAL_OFFSET;
+ // integer part
+ audLatencyPerfValInt = (int32_t)dInt;
- const double c = f->c;
- const double g = f->ci;
- const double cg = f->cg;
- const double bg = f->bg;
- const double c2 = f->c2;
-
- double *v = f->buffer;
-
- // left channel
- const double estimate_L = in_2 + g*(v[1] + c*(in_1 + g*(v[0] + c*in[0])));
- const double y0_L = v[0]*g + in[0]*cg + in_1 + estimate_L * bg;
- const double y1_L = v[1]*g + y0_L*cg + in_2;
-
- v[0] += c2 * (in[0] - y0_L);
- v[1] += c2 * (y0_L - y1_L);
- out[0] = y1_L;
-
- // right channel
- const double estimate_R = in_2 + g*(v[3] + c*(in_1 + g*(v[2] + c*in[1])));
- const double y0_R = v[2]*g + in[1]*cg + in_1 + estimate_R * bg;
- const double y1_R = v[3]*g + y0_R*cg + in_2;
-
- v[2] += c2 * (in[1] - y0_R);
- v[3] += c2 * (y0_R - y1_R);
- out[1] = y1_R;
+ // fractional part (scaled to 0..2^32-1)
+ dFrac *= UINT32_MAX+1.0;
+ audLatencyPerfValFrac = (uint32_t)dFrac;
}
-void calcRCFilterCoeffs(double dSr, double dHz, rcFilter_t *f)
+void setSyncTickTimeLen(uint32_t timeLen, uint32_t timeLenFrac)
{
- const double c = (dHz < (dSr / 2.0)) ? tan((M_PI * dHz) / dSr) : 1.0;
- f->c = c;
- f->c2 = f->c * 2.0;
- f->g = 1.0 / (1.0 + f->c);
- f->cg = f->c * f->g;
+ tickTimeLen = timeLen;
+ tickTimeLenFrac = timeLenFrac;
}
-void clearRCFilterState(rcFilter_t *f)
+static void generateBpmTables(void)
{
- f->buffer[0] = 0.0; // left channel
- f->buffer[1] = 0.0; // right channel
-}
+ for (int32_t i = 32; i <= 255; i++)
+ {
+ const double dBpmHz = i / 2.5;
-// aciddose: input 0 is resistor side of capacitor (low-pass), input 1 is reference side (high-pass)
-static inline double getLowpassOutput(rcFilter_t *f, const double input_0, const double input_1, const double buffer)
-{
- double dOutput = DENORMAL_OFFSET;
-
- dOutput += buffer * f->g + input_0 * f->cg + input_1 * (1.0 - f->cg);
-
- return dOutput;
+ audio.bpmTab[i-32] = audio.outputRate / dBpmHz;
+ audio.bpmTab28kHz[i-32] = PAT2SMP_HI_FREQ / dBpmHz; // PAT2SMP hi quality
+ audio.bpmTab22kHz[i-32] = PAT2SMP_LO_FREQ / dBpmHz; // PAT2SMP low quality
+ audio.bpmTabMod2Wav[i-32] = MOD2WAV_FREQ / dBpmHz; // MOD2WAV
+ }
}
-void RCLowPassFilter(rcFilter_t *f, const double *in, double *out)
-{
- double output;
-
- // left channel RC low-pass
- output = getLowpassOutput(f, in[0], 0.0, f->buffer[0]);
- f->buffer[0] += (in[0] - output) * f->c2;
- out[0] = output;
-
- // right channel RC low-pass
- output = getLowpassOutput(f, in[1], 0.0, f->buffer[1]);
- f->buffer[1] += (in[1] - output) * f->c2;
- out[1] = output;
-}
-
-void RCHighPassFilter(rcFilter_t *f, const double *in, double *out)
-{
- double low[2];
-
- RCLowPassFilter(f, in, low);
-
- out[0] = in[0] - low[0]; // left channel high-pass
- out[1] = in[1] - low[1]; // right channel high-pass
-}
-
-/* These two are used for the filters in the SAMPLER screen, and
-** also the 2x downsampling when loading samples whose frequency
-** is above 22kHz.
-*/
-
-void RCLowPassFilterMono(rcFilter_t *f, const double in, double *out)
-{
- double output = getLowpassOutput(f, in, 0.0, f->buffer[0]);
- f->buffer[0] += (in - output) * f->c2;
- *out = output;
-}
-
-void RCHighPassFilterMono(rcFilter_t *f, const double in, double *out)
-{
- double low;
-
- RCLowPassFilterMono(f, in, &low);
- *out = in - low; // high-pass
-}
-
void lockAudio(void)
{
if (dev != 0)
@@ -331,36 +166,6 @@
}
}
-/* aciddose: these sin/cos approximations both use a 0..1
-** parameter range and have 'normalized' (1/2 = 0db) coeffs
-**
-** the coeffs are for LERP(x, x * x, 0.224) * sqrt(2)
-** max_error is minimized with 0.224 = 0.0013012886
-*/
-static double sinApx(double x)
-{
- x = x * (2.0 - x);
- return x * 1.09742972 + x * x * 0.31678383;
-}
-
-static double cosApx(double x)
-{
- x = (1.0 - x) * (1.0 + x);
- return x * 1.09742972 + x * x * 0.31678383;
-}
-
-static void mixerSetVoicePan(uint8_t ch, uint16_t pan) // pan = 0..256
-{
- /* aciddose: proper 'normalized' equal-power panning is (assuming pan left to right):
- ** L = cos(p * pi * 1/2) * sqrt(2);
- ** R = sin(p * pi * 1/2) * sqrt(2);
- */
- const double dPan = pan * (1.0 / 256.0); // 0.0..1.0
-
- paula[ch].dPanL = cosApx(dPan);
- paula[ch].dPanR = sinApx(dPan);
-}
-
void mixerKillVoice(int32_t ch)
{
const bool audioWasntLocked = !audio.locked;
@@ -397,8 +202,9 @@
clearRCFilterState(&filterLoA500);
clearRCFilterState(&filterLoA1200);
- clearRCFilterState(&filterHi);
- clearLEDFilterState();
+ clearRCFilterState(&filterHiA500);
+ clearRCFilterState(&filterHiA1200);
+ clearLEDFilterState(&filterLED);
resetAudioDithering();
@@ -572,7 +378,7 @@
}
}
-void toggleA500Filters(void)
+void toggleFilterModel(void)
{
const bool audioWasntLocked = !audio.locked;
if (audioWasntLocked)
@@ -580,19 +386,15 @@
clearRCFilterState(&filterLoA500);
clearRCFilterState(&filterLoA1200);
- clearRCFilterState(&filterHi);
- clearLEDFilterState();
+ clearRCFilterState(&filterHiA500);
+ clearRCFilterState(&filterHiA1200);
+ clearLEDFilterState(&filterLED);
- if (filterFlags & FILTER_A500)
- {
- filterFlags &= ~FILTER_A500;
- displayMsg("LP FILTER: A1200");
- }
+ filterModel ^= 1;
+ if (filterModel == FILTERMODEL_A500)
+ displayMsg("AUDIO: AMIGA 500");
else
- {
- filterFlags |= FILTER_A500;
- displayMsg("LP FILTER: A500");
- }
+ displayMsg("AUDIO: AMIGA 1200");
if (audioWasntLocked)
unlockAudio();
@@ -669,77 +471,6 @@
}
}
-void mixChannelsMultiStep(int32_t numSamples) // for PAT2SMP
-{
- double dSmp, dVol;
- blep_t *bSmp, *bVol;
- paulaVoice_t *v;
-
- memset(dMixBufferL, 0, numSamples * sizeof (double));
- memset(dMixBufferR, 0, numSamples * sizeof (double));
-
- v = paula;
- bSmp = blep;
- bVol = blepVol;
-
- for (int32_t i = 0; i < AMIGA_VOICES; i++, v++, bSmp++, bVol++)
- {
- if (!v->active || v->data == NULL)
- continue;
-
- for (int32_t j = 0; j < numSamples; j++)
- {
- assert(v->data != NULL);
- dSmp = v->data[v->pos] * (1.0 / 128.0);
- dVol = v->dVolume;
-
- if (dSmp != bSmp->dLastValue)
- {
- if (v->dLastDelta > v->dLastPhase)
- {
- // div->mul trick: v->dLastDeltaMul is 1.0 / v->dLastDelta
- blepAdd(bSmp, v->dLastPhase * v->dLastDeltaMul, bSmp->dLastValue - dSmp);
- }
-
- bSmp->dLastValue = dSmp;
- }
-
- if (dVol != bVol->dLastValue)
- {
- blepVolAdd(bVol, bVol->dLastValue - dVol);
- bVol->dLastValue = dVol;
- }
-
- if (bSmp->samplesLeft > 0) dSmp = blepRun(bSmp, dSmp);
- if (bVol->samplesLeft > 0) dVol = blepRun(bVol, dVol);
-
- dSmp *= dVol;
-
- dMixBufferL[j] += dSmp * v->dPanL;
- dMixBufferR[j] += dSmp * v->dPanR;
-
- v->dPhase += v->dDelta;
- while (v->dPhase >= 1.0) // deltas can be >= 1.0 here
- {
- v->dPhase -= 1.0;
-
- v->dLastPhase = v->dPhase;
- v->dLastDelta = v->dDelta;
- v->dLastDeltaMul = v->dDeltaMul;
-
- if (++v->pos >= v->length)
- {
- v->pos = 0;
-
- // re-fetch new Paula register values now
- v->length = v->newLength;
- v->data = v->newData;
- }
- }
- }
- }
-}
-
void resetAudioDithering(void)
{
randSeed = INITIAL_DITHER_SEED;
@@ -755,7 +486,7 @@
return randSeed;
}
-static inline void processMixedSamplesA1200(int32_t i, int16_t *out)
+static inline void processMixedSamples(int32_t i, int16_t *out)
{
int32_t smp32;
double dOut[2], dPrng;
@@ -763,57 +494,33 @@
dOut[0] = dMixBufferL[i];
dOut[1] = dMixBufferR[i];
- if (audio.outputRate >= 96000) // cutoff is too high for 44.1kHz/48kHz
+ if (filterModel == FILTERMODEL_A500)
{
- // process low-pass filter
- RCLowPassFilter(&filterLoA1200, dOut, dOut);
- }
+ // A500 low-pass RC filter
+ RCLowPassFilterStereo(&filterLoA500, dOut, dOut);
- // process high-pass filter
- RCHighPassFilter(&filterHi, dOut, dOut);
+ // "LED" Sallen-Key filter
+ if (ledFilterEnabled)
+ LEDFilter(&filterLED, dOut, dOut);
- // normalize and flip phase (A500/A1200 has an inverted audio signal)
- dOut[0] *= -INT16_MAX / (double)AMIGA_VOICES;
- dOut[1] *= -INT16_MAX / (double)AMIGA_VOICES;
+ // A500 high-pass RC filter
+ RCHighPassFilterStereo(&filterHiA500, dOut, dOut);
+ }
+ else
+ {
+ // A1200 low-pass RC filter
+ if (audio.outputRate >= 96000) // cutoff is too high for 44.1kHz/48kHz
+ RCLowPassFilterStereo(&filterLoA1200, dOut, dOut);
- // left channel - 1-bit triangular dithering (high-pass filtered)
- dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
- dOut[0] = (dOut[0] + dPrng) - dPrngStateL;
- dPrngStateL = dPrng;
- smp32 = (int32_t)dOut[0];
- CLAMP16(smp32);
- out[0] = (int16_t)smp32;
+ // "LED" Sallen-Key filter
+ if (ledFilterEnabled)
+ LEDFilter(&filterLED, dOut, dOut);
- // right channel - 1-bit triangular dithering (high-pass filtered)
- dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
- dOut[1] = (dOut[1] + dPrng) - dPrngStateR;
- dPrngStateR = dPrng;
- smp32 = (int32_t)dOut[1];
- CLAMP16(smp32);
- out[1] = (int16_t)smp32;
-}
-
-static inline void processMixedSamplesA1200LED(int32_t i, int16_t *out)
-{
- int32_t smp32;
- double dOut[2], dPrng;
-
- dOut[0] = dMixBufferL[i];
- dOut[1] = dMixBufferR[i];
-
- if (audio.outputRate >= 96000) // cutoff is too high for 44.1kHz/48kHz
- {
- // process low-pass filter
- RCLowPassFilter(&filterLoA1200, dOut, dOut);
+ // A1200 high-pass RC filter
+ RCHighPassFilterStereo(&filterHiA1200, dOut, dOut);
}
- // process "LED" filter
- LEDFilter(&filterLED, dOut, dOut);
-
- // process high-pass filter
- RCHighPassFilter(&filterHi, dOut, dOut);
-
- // normalize and flip phase (A500/A1200 has an inverted audio signal)
+ // normalize and flip phase (A500/A1200 has a phase-inverted audio signal)
dOut[0] *= -INT16_MAX / (double)AMIGA_VOICES;
dOut[1] *= -INT16_MAX / (double)AMIGA_VOICES;
@@ -834,99 +541,9 @@
out[1] = (int16_t)smp32;
}
-static inline void processMixedSamplesA500(int32_t i, int16_t *out)
-{
- int32_t smp32;
- double dOut[2], dPrng;
-
- dOut[0] = dMixBufferL[i];
- dOut[1] = dMixBufferR[i];
-
- // process low-pass filter
- RCLowPassFilter(&filterLoA500, dOut, dOut);
-
- // process high-pass filter
- RCHighPassFilter(&filterHi, dOut, dOut);
-
- dOut[0] *= -INT16_MAX / (double)AMIGA_VOICES;
- dOut[1] *= -INT16_MAX / (double)AMIGA_VOICES;
-
- // left channel - 1-bit triangular dithering (high-pass filtered)
- dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
- dOut[0] = (dOut[0] + dPrng) - dPrngStateL;
- dPrngStateL = dPrng;
- smp32 = (int32_t)dOut[0];
- CLAMP16(smp32);
- out[0] = (int16_t)smp32;
-
- // right channel - 1-bit triangular dithering (high-pass filtered)
- dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
- dOut[1] = (dOut[1] + dPrng) - dPrngStateR;
- dPrngStateR = dPrng;
- smp32 = (int32_t)dOut[1];
- CLAMP16(smp32);
- out[1] = (int16_t)smp32;
-}
-
-static inline void processMixedSamplesA500LED(int32_t i, int16_t *out)
-{
- int32_t smp32;
- double dOut[2], dPrng;
-
- dOut[0] = dMixBufferL[i];
- dOut[1] = dMixBufferR[i];
-
- // process low-pass filter
- RCLowPassFilter(&filterLoA500, dOut, dOut);
-
- // process "LED" filter
- LEDFilter(&filterLED, dOut, dOut);
-
- // process high-pass filter
- RCHighPassFilter(&filterHi, dOut, dOut);
-
- dOut[0] *= -INT16_MAX / (double)AMIGA_VOICES;
- dOut[1] *= -INT16_MAX / (double)AMIGA_VOICES;
-
- // left channel - 1-bit triangular dithering (high-pass filtered)
- dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
- dOut[0] = (dOut[0] + dPrng) - dPrngStateL;
- dPrngStateL = dPrng;
- smp32 = (int32_t)dOut[0];
- CLAMP16(smp32);
- out[0] = (int16_t)smp32;
-
- // right channel - 1-bit triangular dithering (high-pass filtered)
- dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
- dOut[1] = (dOut[1] + dPrng) - dPrngStateR;
- dPrngStateR = dPrng;
- smp32 = (int32_t)dOut[1];
- CLAMP16(smp32);
- out[1] = (int16_t)smp32;
-}
-
-static inline void processMixedSamplesRaw(int32_t i, int16_t *out) // for PAT2SMP
-{
- int32_t smp32;
- double dOut[2];
-
- dOut[0] = dMixBufferL[i];
- dOut[1] = dMixBufferR[i];
-
- // normalize (don't flip the phase this time)
- dOut[0] *= INT16_MAX / (double)AMIGA_VOICES;
- dOut[1] *= INT16_MAX / (double)AMIGA_VOICES;
-
- dOut[0] = (dOut[0] + dOut[1]) * 0.5; // mix to mono
-
- smp32 = (int32_t)dOut[0];
- CLAMP16(smp32);
- *out = (int16_t)smp32;
-}
-
void outputAudio(int16_t *target, int32_t numSamples)
{
- int16_t *outStream, out[2];
+ int16_t out[2];
int32_t i;
if (editor.isSMPRendering)
@@ -934,20 +551,17 @@
// render to sample (PAT2SMP)
int32_t samplesTodo = numSamples;
- if (editor.pat2SmpPos+samplesTodo > MAX_SAMPLE_LEN)
- samplesTodo = MAX_SAMPLE_LEN-editor.pat2SmpPos;
+ if (editor.pat2SmpPos+samplesTodo > MAX_SAMPLE_LEN*2)
+ samplesTodo = (MAX_SAMPLE_LEN*2)-editor.pat2SmpPos;
- mixChannelsMultiStep(samplesTodo);
+ mixChannels(samplesTodo);
- outStream = &editor.pat2SmpBuf[editor.pat2SmpPos];
+ double *dOutStream = &editor.dPat2SmpBuf[editor.pat2SmpPos];
for (i = 0; i < samplesTodo; i++)
- {
- processMixedSamplesRaw(i, out);
- outStream[i] = out[0];
- }
+ dOutStream[i] = dMixBufferL[i] + dMixBufferR[i]; // normalized to -128..127 later
editor.pat2SmpPos += samplesTodo;
- if (editor.pat2SmpPos >= MAX_SAMPLE_LEN)
+ if (editor.pat2SmpPos >= MAX_SAMPLE_LEN*2)
{
editor.smpRenderingDone = true;
updateWindowTitle(MOD_IS_MODIFIED);
@@ -959,53 +573,13 @@
mixChannels(numSamples);
- outStream = target;
- if (filterFlags & FILTER_A500)
+ int16_t *outStream = target;
+ for (i = 0; i < numSamples; i++)
{
- // Amiga 500 filter model
-
- if (filterFlags & FILTER_LED_ENABLED)
- {
- for (i = 0; i < numSamples; i++)
- {
- processMixedSamplesA500LED(i, out);
- *outStream++ = out[0];
- *outStream++ = out[1];
- }
- }
- else
- {
- for (i = 0; i < numSamples; i++)
- {
- processMixedSamplesA500(i, out);
- *outStream++ = out[0];
- *outStream++ = out[1];
- }
- }
+ processMixedSamples(i, out);
+ *outStream++ = out[0];
+ *outStream++ = out[1];
}
- else
- {
- // Amiga 1200 filter model
-
- if (filterFlags & FILTER_LED_ENABLED)
- {
- for (i = 0; i < numSamples; i++)
- {
- processMixedSamplesA1200LED(i, out);
- *outStream++ = out[0];
- *outStream++ = out[1];
- }
- }
- else
- {
- for (i = 0; i < numSamples; i++)
- {
- processMixedSamplesA1200(i, out);
- *outStream++ = out[0];
- *outStream++ = out[1];
- }
- }
- }
}
}
@@ -1147,61 +721,73 @@
** - RC 6dB/oct low-pass: R=680 ohm, C=6800pF (f=34419.321Hz)
** - Sallen-key low-pass ("LED"): Same as A500 (f=3090.532Hz)
** - RC 6dB/oct high-pass: R=1390 ohm (1000+390), C=22uF (f=5.204Hz)
- **
- ** Correct values for A600 (a600_schematics.pdf):
- ** - RC 6dB/oct low-pass: Same as A500 (f=4420.970Hz)
- ** - Sallen-key low-pass ("LED"): Same as A500 (f=3090.532Hz)
- ** - RC 6dB/oct high-pass: Same as A1200 (f=5.204Hz)
*/
double R, C, R1, R2, C1, C2, fc, fb;
+ const double pi = 4.0 * atan(1.0); // M_PI can not be trusted
+ // A500 1-pole (6db/oct) static RC low-pass filter:
+ R = 360.0; // R321 (360 ohm resistor)
+ C = 1e-7; // C321 (0.1uF capacitor)
+ fc = 1.0 / (2.0 * pi * R * C);
+ calcRCFilterCoeffs(audio.outputRate, fc, &filterLoA500);
+
+ // A1200 1-pole (6db/oct) static RC low-pass filter:
if (audio.outputRate >= 96000) // cutoff is too high for 44.1kHz/48kHz
{
- // A1200 1-pole (6db/oct) static RC low-pass filter:
R = 680.0; // R321 (680 ohm resistor)
C = 6.8e-9; // C321 (6800pf capacitor)
- fc = 1.0 / (2.0 * M_PI * R * C);
+ fc = 1.0 / (2.0 * pi * R * C);
calcRCFilterCoeffs(audio.outputRate, fc, &filterLoA1200);
}
- // A500 1-pole (6db/oct) static RC low-pass filter:
- R = 360.0; // R321 (360 ohm resistor)
- C = 1e-7; // C321 (0.1uF capacitor)
- fc = 1.0 / (2.0 * M_PI * R * C);
- calcRCFilterCoeffs(audio.outputRate, fc, &filterLoA500);
-
- // Sallen-Key filter ("LED"):
+ // Sallen-Key filter ("LED" filter, same RC values on A500 and A1200):
R1 = 10000.0; // R322 (10K ohm resistor)
R2 = 10000.0; // R323 (10K ohm resistor)
C1 = 6.8e-9; // C322 (6800pF capacitor)
C2 = 3.9e-9; // C323 (3900pF capacitor)
- fc = 1.0 / (2.0 * M_PI * sqrt(R1 * R2 * C1 * C2));
+ fc = 1.0 / (2.0 * pi * sqrt(R1 * R2 * C1 * C2));
fb = 0.125; // Fb = 0.125 : Q ~= 1/sqrt(2)
calcLEDFilterCoeffs(audio.outputRate, fc, fb, &filterLED);
+ // A500 1-pole (6db/oct) static RC high-pass filter:
+ R = 1390.0; // R324 (1K ohm resistor) + R325 (390 ohm resistor)
+ C = 2.233e-5; // C334 (22uF capacitor) + C335 (0.33�F capacitor)
+ fc = 1.0 / (2.0 * pi * R * C);
+ calcRCFilterCoeffs(audio.outputRate, fc, &filterHiA500);
+
// A1200 1-pole (6db/oct) static RC high-pass filter:
R = 1390.0; // R324 (1K ohm resistor) + R325 (390 ohm resistor)
C = 2.2e-5; // C334 (22uF capacitor)
- fc = 1.0 / (2.0 * M_PI * R * C);
- calcRCFilterCoeffs(audio.outputRate, fc, &filterHi);
+ fc = 1.0 / (2.0 * pi * R * C);
+ calcRCFilterCoeffs(audio.outputRate, fc, &filterHiA1200);
}
-void mixerCalcVoicePans(uint8_t stereoSeparation)
+static void setVoicePan(int32_t ch, double pan) // pan = 0.0 .. 1.0
{
- const uint8_t scaledPanPos = (stereoSeparation * 128) / 100;
+ // constant power panning
- ch1Pan = 128 - scaledPanPos;
- ch2Pan = 128 + scaledPanPos;
- ch3Pan = 128 + scaledPanPos;
- ch4Pan = 128 - scaledPanPos;
+ const double pi = 4.0 * atan(1.0); // M_PI can not be trusted
- mixerSetVoicePan(0, ch1Pan);
- mixerSetVoicePan(1, ch2Pan);
- mixerSetVoicePan(2, ch3Pan);
- mixerSetVoicePan(3, ch4Pan);
+ paula[ch].dPanL = cos(pan * pi * 0.5) * sqrt(2.0);
+ paula[ch].dPanR = sin(pan * pi * 0.5) * sqrt(2.0);
}
+void mixerCalcVoicePans(uint8_t stereoSeparation) // 0..100 (percentage)
+{
+ assert(stereoSeparation <= 100);
+
+ const double panMid = 0.5;
+
+ const double panR = panMid + (stereoSeparation / (100.0 * 2.0));
+ const double panL = 1.0 - panR;
+
+ setVoicePan(0, panL);
+ setVoicePan(1, panR);
+ setVoicePan(2, panR);
+ setVoicePan(3, panL);
+}
+
bool setupAudio(void)
{
SDL_AudioSpec want, have;
@@ -1238,16 +824,12 @@
generateBpmTables();
- /* If the audio output rate is lower than MOD2WAV_FREQ, we need
- ** to allocate slightly more space so that MOD2WAV rendering
- ** won't overflow.
- */
+ const int32_t lowestBPM = 32;
+ const int32_t pat2SmpMaxSamples = (int32_t)ceil(audio.bpmTab22kHz[lowestBPM-32]);
+ const int32_t mod2WavMaxSamples = (int32_t)ceil(audio.bpmTabMod2Wav[lowestBPM-32]);
+ const int32_t renderMaxSamples = (int32_t)ceil(audio.bpmTab[lowestBPM-32]);
- int32_t maxSamplesToMix;
- if (MOD2WAV_FREQ > audio.outputRate)
- maxSamplesToMix = (int32_t)ceil(audio.bpmTabMod2Wav[32-32]); // BPM 32
- else
- maxSamplesToMix = (int32_t)ceil(audio.bpmTab[32-32]); // BPM 32
+ const int32_t maxSamplesToMix = MAX(pat2SmpMaxSamples, MAX(mod2WavMaxSamples, renderMaxSamples));
dMixBufferLUnaligned = (double *)MALLOC_PAD(maxSamplesToMix * sizeof (double), 256);
dMixBufferRUnaligned = (double *)MALLOC_PAD(maxSamplesToMix * sizeof (double), 256);
@@ -1264,7 +846,8 @@
mixerCalcVoicePans(config.stereoSeparation);
defStereoSep = config.stereoSeparation;
- filterFlags = config.a500LowPassFilter ? FILTER_A500 : 0;
+ filterModel = config.filterModel;
+ ledFilterEnabled = false;
calculateFilterCoeffs();
audio.dSamplesPerTick = audio.bpmTab[125-32]; // BPM 125
@@ -1335,97 +918,104 @@
unlockAudio();
}
-void normalize32bitSigned(int32_t *sampleData, uint32_t sampleLength)
+uint16_t get16BitPeak(int16_t *sampleData, uint32_t sampleLength)
{
- int32_t sample, sampleVolPeak;
- uint32_t i;
- double dGain;
-
- sampleVolPeak = 0;
- for (i = 0; i < sampleLength; i++)
+ uint16_t samplePeak = 0;
+ for (uint32_t i = 0; i < sampleLength; i++)
{
- sample = ABS(sampleData[i]);
- if (sampleVolPeak < sample)
- sampleVolPeak = sample;
+ uint16_t sample = ABS(sampleData[i]);
+ if (samplePeak < sample)
+ samplePeak = sample;
}
- if (sampleVolPeak >= INT32_MAX)
- return; // sample is already normalized
+ return samplePeak;
+}
- // prevent division by zero!
- if (sampleVolPeak <= 0)
- sampleVolPeak = 1;
-
- dGain = (double)INT32_MAX / sampleVolPeak;
- for (i = 0; i < sampleLength; i++)
+uint32_t get32BitPeak(int32_t *sampleData, uint32_t sampleLength)
+{
+ uint32_t samplePeak = 0;
+ for (uint32_t i = 0; i < sampleLength; i++)
{
- sample = (int32_t)(sampleData[i] * dGain);
- sampleData[i] = (int32_t)sample;
+ uint32_t sample = ABS(sampleData[i]);
+ if (samplePeak < sample)
+ samplePeak = sample;
}
+
+ return samplePeak;
}
-void normalize16bitSigned(int16_t *sampleData, uint32_t sampleLength)
+float getFloatPeak(float *fSampleData, uint32_t sampleLength)
{
- uint32_t i;
- int32_t sample, sampleVolPeak, gain;
+ float fSamplePeak = 0.0f;
+ for (uint32_t i = 0; i < sampleLength; i++)
+ {
+ const float fSample = fabsf(fSampleData[i]);
+ if (fSamplePeak < fSample)
+ fSamplePeak = fSample;
+ }
- sampleVolPeak = 0;
- for (i = 0; i < sampleLength; i++)
+ return fSamplePeak;
+}
+
+double getDoublePeak(double *dSampleData, uint32_t sampleLength)
+{
+ double dSamplePeak = 0.0;
+ for (uint32_t i = 0; i < sampleLength; i++)
{
- sample = ABS(sampleData[i]);
- if (sampleVolPeak < sample)
- sampleVolPeak = sample;
+ const double dSample = fabs(dSampleData[i]);
+ if (dSamplePeak < dSample)
+ dSamplePeak = dSample;
}
- if (sampleVolPeak >= INT16_MAX)
- return; // sample is already normalized
+ return dSamplePeak;
+}
- if (sampleVolPeak < 1)
+void normalize16BitTo8Bit(int16_t *sampleData, uint32_t sampleLength)
+{
+ const uint16_t samplePeak = get16BitPeak(sampleData, sampleLength);
+ if (samplePeak == 0 || samplePeak >= INT16_MAX)
return;
- gain = (INT16_MAX * 65536) / sampleVolPeak;
- for (i = 0; i < sampleLength; i++)
- sampleData[i] = (int16_t)((sampleData[i] * gain) >> 16);
+ const double dGain = (double)INT16_MAX / samplePeak;
+ for (uint32_t i = 0; i < sampleLength; i++)
+ {
+ const int32_t sample = (const int32_t)(sampleData[i] * dGain);
+ sampleData[i] = (int16_t)sample;
+ }
}
-void normalize8bitFloatSigned(float *fSampleData, uint32_t sampleLength)
+void normalize32BitTo8Bit(int32_t *sampleData, uint32_t sampleLength)
{
- uint32_t i;
- float fSample, fSampleVolPeak, fGain;
+ const uint32_t samplePeak = get32BitPeak(sampleData, sampleLength);
+ if (samplePeak == 0 || samplePeak >= INT32_MAX)
+ return;
- fSampleVolPeak = 0.0f;
- for (i = 0; i < sampleLength; i++)
+ const double dGain = (double)INT32_MAX / samplePeak;
+ for (uint32_t i = 0; i < sampleLength; i++)
{
- fSample = fabsf(fSampleData[i]);
- if (fSampleVolPeak < fSample)
- fSampleVolPeak = fSample;
+ const int32_t sample = (const int32_t)(sampleData[i] * dGain);
+ sampleData[i] = (int32_t)sample;
}
+}
- if (fSampleVolPeak <= 0.0f)
+void normalizeFloatTo8Bit(float *fSampleData, uint32_t sampleLength)
+{
+ const float fSamplePeak = getFloatPeak(fSampleData, sampleLength);
+ if (fSamplePeak <= 0.0f)
return;
- fGain = INT8_MAX / fSampleVolPeak;
- for (i = 0; i < sampleLength; i++)
+ const float fGain = INT8_MAX / fSamplePeak;
+ for (uint32_t i = 0; i < sampleLength; i++)
fSampleData[i] *= fGain;
}
-void normalize8bitDoubleSigned(double *dSampleData, uint32_t sampleLength)
+void normalizeDoubleTo8Bit(double *dSampleData, uint32_t sampleLength)
{
- uint32_t i;
- double dSample, dSampleVolPeak, dGain;
-
- dSampleVolPeak = 0.0;
- for (i = 0; i < sampleLength; i++)
- {
- dSample = fabs(dSampleData[i]);
- if (dSampleVolPeak < dSample)
- dSampleVolPeak = dSample;
- }
-
- if (dSampleVolPeak <= 0.0)
+ const double dSamplePeak = getDoublePeak(dSampleData, sampleLength);
+ if (dSamplePeak <= 0.0)
return;
- dGain = INT8_MAX / dSampleVolPeak;
- for (i = 0; i < sampleLength; i++)
+ const double dGain = INT8_MAX / dSamplePeak;
+ for (uint32_t i = 0; i < sampleLength; i++)
dSampleData[i] *= dGain;
}
--- a/src/pt2_audio.h
+++ b/src/pt2_audio.h
@@ -4,15 +4,6 @@
#include <stdbool.h>
#include "pt2_header.h" // AMIGA_VOICES
-// adding this prevents denormalized numbers, which is slow
-#define DENORMAL_OFFSET 1e-15
-
-typedef struct rcFilter_t
-{
- double buffer[2];
- double c, c2, g, cg;
-} rcFilter_t;
-
typedef struct audio_t
{
volatile bool locked, isSampling;
@@ -51,20 +42,20 @@
void setSyncTickTimeLen(uint32_t timeLen, uint32_t timeLenFrac);
void resetCachedMixerPeriod(void);
void resetAudioDithering(void);
-void calcRCFilterCoeffs(const double sr, const double hz, rcFilter_t *f);
-void clearRCFilterState(rcFilter_t *f);
-void RCLowPassFilter(rcFilter_t *f, const double *in, double *out);
-void RCHighPassFilter(rcFilter_t *f, const double *in, double *out);
-void RCLowPassFilterMono(rcFilter_t *f, const double in, double *out);
-void RCHighPassFilterMono(rcFilter_t *f, const double in, double *out);
-void normalize32bitSigned(int32_t *sampleData, uint32_t sampleLength);
-void normalize16bitSigned(int16_t *sampleData, uint32_t sampleLength);
-void normalize8bitFloatSigned(float *fSampleData, uint32_t sampleLength);
-void normalize8bitDoubleSigned(double *dSampleData, uint32_t sampleLength);
+
+uint16_t get16BitPeak(int16_t *sampleData, uint32_t sampleLength);
+uint32_t get32BitPeak(int32_t *sampleData, uint32_t sampleLength);
+float getFloatPeak(float *fSampleData, uint32_t sampleLength);
+double getDoublePeak(double *dSampleData, uint32_t sampleLength);
+void normalize16BitTo8Bit(int16_t *sampleData, uint32_t sampleLength);
+void normalize32BitTo8Bit(int32_t *sampleData, uint32_t sampleLength);
+void normalizeFloatTo8Bit(float *fSampleData, uint32_t sampleLength);
+void normalizeDoubleTo8Bit(double *dSampleData, uint32_t sampleLength);
+
void setLEDFilter(bool state, bool doLockAudio);
void toggleLEDFilter(void);
void toggleAmigaPanMode(void);
-void toggleA500Filters(void);
+void toggleFilterModel(void);
void paulaStopDMA(int32_t ch);
void paulaStartDMA(int32_t ch);
void paulaSetPeriod(int32_t ch, uint16_t period);
--- a/src/pt2_blep.c
+++ b/src/pt2_blep.c
@@ -1,7 +1,6 @@
// these BLEP routines were coded by aciddose
#include <stdint.h>
-#include <stdbool.h>
#include <assert.h>
#include "pt2_blep.h"
--- /dev/null
+++ b/src/pt2_chordmaker.c
@@ -1,0 +1,594 @@
+// for finding memory leaks in debug mode with Visual Studio
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "pt2_header.h"
+#include "pt2_mouse.h"
+#include "pt2_textout.h"
+#include "pt2_visuals.h"
+#include "pt2_structs.h"
+#include "pt2_helpers.h"
+#include "pt2_tables.h"
+#include "pt2_sampler.h"
+#include "pt2_audio.h"
+#include "pt2_blep.h"
+#include "pt2_downsamplers2x.h"
+
+#define MAX_NOTES 4
+
+typedef struct sampleMixer_t
+{
+ bool active;
+ int32_t length, pos;
+ double dDelta, dPhase, dLastPhase;
+} sampleMixer_t;
+
+static void sortNotes(void)
+{
+ for (int32_t i = 0; i < 3; i++)
+ {
+ if (editor.note2 == 36)
+ {
+ editor.note2 = editor.note3;
+ editor.note3 = editor.note4;
+ editor.note4 = 36;
+ }
+ }
+
+ for (int32_t i = 0; i < 3; i++)
+ {
+ if (editor.note3 == 36)
+ {
+ editor.note3 = editor.note4;
+ editor.note4 = 36;
+ }
+ }
+}
+
+static void removeDuplicateNotes(void)
+{
+ if (editor.note4 == editor.note3) editor.note4 = 36;
+ if (editor.note4 == editor.note2) editor.note4 = 36;
+ if (editor.note3 == editor.note2) editor.note3 = 36;
+}
+
+static void setupMixVoice(sampleMixer_t *v, int32_t length, double dDelta)
+{
+ v->dDelta = dDelta;
+ v->length = (int32_t)ceil(length / dDelta);
+
+ if (v->length > 0)
+ v->active = true;
+}
+
+// this has 2x oversampling for BLEP to function properly with all pitches
+void mixChordSample(void)
+{
+ bool smpLoopFlag;
+ char smpText[22+1];
+ int8_t *smpData, sameNotes, smpVolume;
+ uint8_t smpFinetune, finetune;
+ int32_t i, smpLoopStart, smpLoopLength, smpEnd;
+ sampleMixer_t mixCh[MAX_NOTES];
+ moduleSample_t *s;
+ blep_t blep[MAX_NOTES];
+
+ if (editor.sampleZero)
+ {
+ statusNotSampleZero();
+ return;
+ }
+
+ if (editor.note1 == 36)
+ {
+ displayErrorMsg("NO BASENOTE!");
+ return;
+ }
+
+ s = &song->samples[editor.currSample];
+ if (s->length == 0)
+ {
+ statusSampleIsEmpty();
+ return;
+ }
+
+ // check if all notes are the same (illegal)
+ sameNotes = true;
+ if (editor.note2 != 36 && editor.note2 != editor.note1) sameNotes = false; else editor.note2 = 36;
+ if (editor.note3 != 36 && editor.note3 != editor.note1) sameNotes = false; else editor.note3 = 36;
+ if (editor.note4 != 36 && editor.note4 != editor.note1) sameNotes = false; else editor.note4 = 36;
+
+ if (sameNotes)
+ {
+ displayErrorMsg("ONLY ONE NOTE!");
+ return;
+ }
+
+ sortNotes();
+ removeDuplicateNotes();
+
+ ui.updateNote1Text = true;
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ // setup some variables
+ smpLoopStart = s->loopStart;
+ smpLoopLength = s->loopLength;
+ smpLoopFlag = (smpLoopStart + smpLoopLength) > 2;
+ smpEnd = smpLoopFlag ? (smpLoopStart + smpLoopLength) : s->length;
+ smpData = &song->sampleData[s->offset];
+
+ if (editor.newOldFlag == 0)
+ {
+ // find a free sample slot for the new sample
+
+ for (i = 0; i < MOD_SAMPLES; i++)
+ {
+ if (song->samples[i].length == 0)
+ break;
+ }
+
+ if (i == MOD_SAMPLES)
+ {
+ displayErrorMsg("NO EMPTY SAMPLE!");
+ return;
+ }
+
+ smpFinetune = s->fineTune;
+ smpVolume = s->volume;
+ memcpy(smpText, s->text, sizeof (smpText));
+
+ s = &song->samples[i];
+ s->fineTune = smpFinetune;
+ s->volume = smpVolume;
+
+ memcpy(s->text, smpText, sizeof (smpText));
+ editor.currSample = (int8_t)i;
+ }
+
+ double *dMixData = (double *)calloc(MAX_SAMPLE_LEN*2, sizeof (double));
+ if (dMixData == NULL)
+ {
+ statusOutOfMemory();
+ return;
+ }
+
+ s->length = smpLoopFlag ? MAX_SAMPLE_LEN : editor.chordLength; // if sample loops, set max length
+ s->loopLength = 2;
+ s->loopStart = 0;
+ s->text[21] = '!'; // chord sample indicator
+ s->text[22] = '\0';
+
+ memset(mixCh, 0, sizeof (mixCh));
+
+ // setup mixing lengths and deltas
+
+ finetune = s->fineTune & 0xF;
+ const double dOutputHz = ((double)PAULA_PAL_CLK / periodTable[24]) * 2.0;
+
+ const double dClk = PAULA_PAL_CLK / dOutputHz;
+ if (editor.note1 < 36) setupMixVoice(&mixCh[0], smpEnd, dClk / periodTable[(finetune * 37) + editor.note1]);
+ if (editor.note2 < 36) setupMixVoice(&mixCh[1], smpEnd, dClk / periodTable[(finetune * 37) + editor.note2]);
+ if (editor.note3 < 36) setupMixVoice(&mixCh[2], smpEnd, dClk / periodTable[(finetune * 37) + editor.note3]);
+ if (editor.note4 < 36) setupMixVoice(&mixCh[3], smpEnd, dClk / periodTable[(finetune * 37) + editor.note4]);
+
+ // start mixing
+ memset(blep, 0, sizeof (blep));
+ turnOffVoices();
+
+ sampleMixer_t *v = mixCh;
+ blep_t *bSmp = blep;
+
+ for (i = 0; i < MAX_NOTES; i++, v++, bSmp++)
+ {
+ if (!v->active || v->dDelta == 0.0)
+ continue;
+
+ for (int32_t j = 0; j < MAX_SAMPLE_LEN*2; j++)
+ {
+ double dSmp = smpData[v->pos] * (1.0 / 128.0);
+
+ if (dSmp != bSmp->dLastValue)
+ {
+ if (v->dDelta > v->dLastPhase)
+ blepAdd(bSmp, v->dLastPhase / v->dDelta, bSmp->dLastValue - dSmp);
+
+ bSmp->dLastValue = dSmp;
+ }
+
+ if (bSmp->samplesLeft > 0) dSmp = blepRun(bSmp, dSmp);
+
+ dMixData[j] += dSmp;
+
+ v->dPhase += v->dDelta;
+ if (v->dPhase >= 1.0)
+ {
+ v->dPhase -= 1.0;
+ v->dLastPhase = v->dPhase;
+
+ if (++v->pos >= smpEnd)
+ {
+ if (smpLoopFlag)
+ {
+ do
+ {
+ v->pos -= smpLoopLength;
+ }
+ while (v->pos >= smpEnd);
+ }
+ else break; // we should insert an ending blep here, but I lost that code ages ago...
+ }
+ }
+ }
+ }
+
+ // downsample oversampled buffer, normalize and quantize to 8-bit
+
+ downsample2xDouble(dMixData, s->length * 2);
+
+ double dAmp = 1.0;
+ const double dPeak = getDoublePeak(dMixData, s->length);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+
+ int8_t *smpPtr = &song->sampleData[s->offset];
+ for (i = 0; i < s->length; i++)
+ {
+ const int32_t smp = (const int32_t)round(dMixData[i] * dAmp);
+ assert(smp >= -128 && smp <= 127); // shouldn't happen according to dAmp (but just in case)
+ smpPtr[i] = (int8_t)smp;
+ }
+
+ free(dMixData);
+
+ // clear unused sample data (if sample is not full already)
+ if (s->length < MAX_SAMPLE_LEN)
+ memset(&song->sampleData[s->offset + s->length], 0, MAX_SAMPLE_LEN - s->length);
+
+ // we're done
+
+ editor.samplePos = 0;
+ fixSampleBeep(s);
+ updateCurrSample();
+
+ updateWindowTitle(MOD_IS_MODIFIED);
+}
+
+static void backupChord(void)
+{
+ editor.oldNote1 = editor.note1;
+ editor.oldNote2 = editor.note2;
+ editor.oldNote3 = editor.note3;
+ editor.oldNote4 = editor.note4;
+}
+
+void recalcChordLength(void)
+{
+ int8_t note;
+ int32_t len;
+ moduleSample_t *s;
+
+ s = &song->samples[editor.currSample];
+
+ if (editor.chordLengthMin)
+ {
+ note = MAX(MAX((editor.note1 == 36) ? -1 : editor.note1,
+ (editor.note2 == 36) ? -1 : editor.note2),
+ MAX((editor.note3 == 36) ? -1 : editor.note3,
+ (editor.note4 == 36) ? -1 : editor.note4));
+ }
+ else
+ {
+ note = MIN(MIN(editor.note1, editor.note2), MIN(editor.note3, editor.note4));
+ }
+
+ if (note < 0 || note > 35)
+ {
+ editor.chordLength = 0;
+ }
+ else
+ {
+ len = (s->length * periodTable[(37 * s->fineTune) + note]) / periodTable[24];
+ if (len > MAX_SAMPLE_LEN)
+ len = MAX_SAMPLE_LEN;
+
+ editor.chordLength = len & 0xFFFE;
+ }
+
+ if (ui.editOpScreenShown && ui.editOpScreen == 3)
+ ui.updateLengthText = true;
+}
+
+void resetChord(void)
+{
+ editor.note1 = 36;
+ editor.note2 = 36;
+ editor.note3 = 36;
+ editor.note4 = 36;
+
+ editor.chordLengthMin = false;
+
+ ui.updateNote1Text = true;
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ recalcChordLength();
+}
+
+void undoChord(void)
+{
+ editor.note1 = editor.oldNote1;
+ editor.note2 = editor.oldNote2;
+ editor.note3 = editor.oldNote3;
+ editor.note4 = editor.oldNote4;
+
+ ui.updateNote1Text = true;
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ recalcChordLength();
+}
+
+void toggleChordLength(void)
+{
+ if (song->samples[editor.currSample].loopLength == 2 && song->samples[editor.currSample].loopStart == 0)
+ {
+ editor.chordLengthMin = mouse.rightButtonPressed ? true : false;
+ recalcChordLength();
+ }
+}
+
+void setChordMajor(void)
+{
+ if (editor.note1 == 36)
+ {
+ displayErrorMsg("NO BASENOTE!");
+ return;
+ }
+
+ backupChord();
+
+ editor.note2 = editor.note1 + 4;
+ editor.note3 = editor.note1 + 7;
+
+ if (editor.note2 >= 36) editor.note2 -= 12;
+ if (editor.note3 >= 36) editor.note3 -= 12;
+
+ editor.note4 = 36;
+
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ recalcChordLength();
+}
+
+void setChordMinor(void)
+{
+ if (editor.note1 == 36)
+ {
+ displayErrorMsg("NO BASENOTE!");
+ return;
+ }
+
+ backupChord();
+
+ editor.note2 = editor.note1 + 3;
+ editor.note3 = editor.note1 + 7;
+
+ if (editor.note2 >= 36) editor.note2 -= 12;
+ if (editor.note3 >= 36) editor.note3 -= 12;
+
+ editor.note4 = 36;
+
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ recalcChordLength();
+}
+
+void setChordSus4(void)
+{
+ if (editor.note1 == 36)
+ {
+ displayErrorMsg("NO BASENOTE!");
+ return;
+ }
+
+ backupChord();
+
+ editor.note2 = editor.note1 + 5;
+ editor.note3 = editor.note1 + 7;
+
+ if (editor.note2 >= 36) editor.note2 -= 12;
+ if (editor.note3 >= 36) editor.note3 -= 12;
+
+ editor.note4 = 36;
+
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ recalcChordLength();
+}
+
+void setChordMajor6(void)
+{
+ if (editor.note1 == 36)
+ {
+ displayErrorMsg("NO BASENOTE!");
+ return;
+ }
+
+ backupChord();
+
+ editor.note2 = editor.note1 + 4;
+ editor.note3 = editor.note1 + 7;
+ editor.note4 = editor.note1 + 9;
+
+ if (editor.note2 >= 36) editor.note2 -= 12;
+ if (editor.note3 >= 36) editor.note3 -= 12;
+ if (editor.note4 >= 36) editor.note4 -= 12;
+
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ recalcChordLength();
+}
+
+void setChordMinor6(void)
+{
+ if (editor.note1 == 36)
+ {
+ displayErrorMsg("NO BASENOTE!");
+ return;
+ }
+
+ backupChord();
+
+ editor.note2 = editor.note1 + 3;
+ editor.note3 = editor.note1 + 7;
+ editor.note4 = editor.note1 + 9;
+
+ if (editor.note2 >= 36) editor.note2 -= 12;
+ if (editor.note3 >= 36) editor.note3 -= 12;
+ if (editor.note4 >= 36) editor.note4 -= 12;
+
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ recalcChordLength();
+}
+
+void setChordMajor7(void)
+{
+ if (editor.note1 == 36)
+ {
+ displayErrorMsg("NO BASENOTE!");
+ return;
+ }
+
+ backupChord();
+
+ editor.note2 = editor.note1 + 4;
+ editor.note3 = editor.note1 + 7;
+ editor.note4 = editor.note1 + 11;
+
+ if (editor.note2 >= 36) editor.note2 -= 12;
+ if (editor.note3 >= 36) editor.note3 -= 12;
+ if (editor.note4 >= 36) editor.note4 -= 12;
+
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ recalcChordLength();
+}
+
+void setChordMinor7(void)
+{
+ if (editor.note1 == 36)
+ {
+ displayErrorMsg("NO BASENOTE!");
+ return;
+ }
+
+ backupChord();
+
+ editor.note2 = editor.note1 + 3;
+ editor.note3 = editor.note1 + 7;
+ editor.note4 = editor.note1 + 10;
+
+ if (editor.note2 >= 36) editor.note2 -= 12;
+ if (editor.note3 >= 36) editor.note3 -= 12;
+ if (editor.note4 >= 36) editor.note4 -= 12;
+
+ ui.updateNote2Text = true;
+ ui.updateNote3Text = true;
+ ui.updateNote4Text = true;
+
+ recalcChordLength();
+}
+
+void selectChordNote1(void)
+{
+ if (mouse.rightButtonPressed)
+ {
+ editor.note1 = 36;
+ }
+ else
+ {
+ ui.changingChordNote = 1;
+ setStatusMessage("SELECT NOTE", NO_CARRY);
+ pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+ }
+
+ ui.updateNote1Text = true;
+}
+
+void selectChordNote2(void)
+{
+ if (mouse.rightButtonPressed)
+ {
+ editor.note2 = 36;
+ }
+ else
+ {
+ ui.changingChordNote = 2;
+ setStatusMessage("SELECT NOTE", NO_CARRY);
+ pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+ }
+
+ ui.updateNote2Text = true;
+}
+
+void selectChordNote3(void)
+{
+ if (mouse.rightButtonPressed)
+ {
+ editor.note3 = 36;
+ }
+ else
+ {
+ ui.changingChordNote = 3;
+ setStatusMessage("SELECT NOTE", NO_CARRY);
+ pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+ }
+
+ ui.updateNote3Text = true;
+}
+
+void selectChordNote4(void)
+{
+ if (mouse.rightButtonPressed)
+ {
+ editor.note4 = 36;
+ }
+ else
+ {
+ ui.changingChordNote = 4;
+ setStatusMessage("SELECT NOTE", NO_CARRY);
+ pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+ }
+
+ ui.updateNote4Text = true;
+}
+
+void makeChord(void)
+{
+ ui.askScreenShown = true;
+ ui.askScreenType = ASK_MAKE_CHORD;
+ pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+ setStatusMessage("MAKE CHORD?", NO_CARRY);
+ renderAskDialog();
+}
--- /dev/null
+++ b/src/pt2_chordmaker.h
@@ -1,0 +1,18 @@
+#pragma once
+
+void recalcChordLength(void);
+void resetChord(void);
+void undoChord(void);
+void toggleChordLength(void);
+void setChordMajor(void);
+void setChordMinor(void);
+void setChordSus4(void);
+void setChordMajor6(void);
+void setChordMinor6(void);
+void setChordMajor7(void);
+void setChordMinor7(void);
+void selectChordNote1(void);
+void selectChordNote2(void);
+void selectChordNote3(void);
+void selectChordNote4(void);
+void makeChord(void);
--- a/src/pt2_config.c
+++ b/src/pt2_config.c
@@ -45,8 +45,8 @@
config.fullScreenStretch = false;
config.pattDots = false;
config.waveformCenterLine = true;
- config.a500LowPassFilter = false;
- config.soundFrequency = 96000;
+ config.filterModel = FILTERMODEL_A1200;
+ config.soundFrequency = 48000;
config.rememberPlayMode = false;
config.stereoSeparation = 20;
config.videoScaleFactor = 2;
@@ -61,12 +61,10 @@
config.autoCloseDiskOp = true;
config.vsyncOff = false;
config.hwMouse = true;
- config.sampleLowpass = true;
config.startInFullscreen = false;
config.pixelFilter = PIXELFILTER_NEAREST;
config.integerScaling = true;
config.audioInputFrequency = 44100;
- config.normalizeSampling = true;
#ifndef _WIN32
getcwd(oldCwd, PATH_MAX);
@@ -201,13 +199,6 @@
else if (!_strnicmp(&configLine[8], "FALSE", 5)) config.hwMouse = false;
}
- // SAMPLELOWPASS
- else if (!_strnicmp(configLine, "SAMPLELOWPASS=", 14))
- {
- if (!_strnicmp(&configLine[14], "TRUE", 4)) config.sampleLowpass = true;
- else if (!_strnicmp(&configLine[14], "FALSE", 5)) config.sampleLowpass = false;
- }
-
// VSYNCOFF
else if (!_strnicmp(configLine, "VSYNCOFF=", 9))
{
@@ -375,18 +366,25 @@
}
}
- // A500LOWPASSFILTER
+ // FILTERMODEL
+ else if (!_strnicmp(configLine, "FILTERMODEL=", 12))
+ {
+ if (!_strnicmp(&configLine[12], "A500", 4)) config.filterModel = FILTERMODEL_A500;
+ else if (!_strnicmp(&configLine[12], "A1200", 5)) config.filterModel = FILTERMODEL_A1200;
+ }
+
+ // A500LOWPASSFILTER (deprecated, same as A4000LOWPASSFILTER)
else if (!_strnicmp(configLine, "A500LOWPASSFILTER=", 18))
{
- if (!_strnicmp(&configLine[18], "TRUE", 4)) config.a500LowPassFilter = true;
- else if (!_strnicmp(&configLine[18], "FALSE", 5)) config.a500LowPassFilter = false;
+ if (!_strnicmp(&configLine[18], "TRUE", 4)) config.filterModel = FILTERMODEL_A500;
+ else if (!_strnicmp(&configLine[18], "FALSE", 5)) config.filterModel = FILTERMODEL_A1200;
}
- // A4000LOWPASSFILTER (deprecated, same as A500LOWPASSFILTER)
+ // A4000LOWPASSFILTER (deprecated)
else if (!_strnicmp(configLine, "A4000LOWPASSFILTER=", 19))
{
- if (!_strnicmp(&configLine[19], "TRUE", 4)) config.a500LowPassFilter = true;
- else if (!_strnicmp(&configLine[19], "FALSE", 5)) config.a500LowPassFilter = false;
+ if (!_strnicmp(&configLine[19], "TRUE", 4)) config.filterModel = FILTERMODEL_A500;
+ else if (!_strnicmp(&configLine[19], "FALSE", 5)) config.filterModel = FILTERMODEL_A1200;
}
// SAMPLINGFREQ
@@ -397,13 +395,6 @@
const int32_t num = atoi(&configLine[13]);
config.audioInputFrequency = CLAMP(num, 44100, 192000);
}
- }
-
- // NORMALIZESAMPLING
- else if (!_strnicmp(configLine, "NORMALIZESAMPLING=", 18))
- {
- if (!_strnicmp(&configLine[18], "TRUE", 4)) config.normalizeSampling = true;
- else if (!_strnicmp(&configLine[18], "FALSE", 5)) config.normalizeSampling = false;
}
// FREQUENCY
--- a/src/pt2_config.h
+++ b/src/pt2_config.h
@@ -13,11 +13,11 @@
typedef struct config_t
{
char *defModulesDir, *defSamplesDir;
- bool waveformCenterLine, pattDots, a500LowPassFilter, compoMode, autoCloseDiskOp, hideDiskOpDates, hwMouse;
+ bool waveformCenterLine, pattDots, compoMode, autoCloseDiskOp, hideDiskOpDates, hwMouse;
bool transDel, fullScreenStretch, vsyncOff, modDot, blankZeroFlag, realVuMeters, rememberPlayMode;
- bool sampleLowpass, startInFullscreen, integerScaling, normalizeSampling;
+ bool startInFullscreen, integerScaling;
int8_t stereoSeparation, videoScaleFactor, accidental;
- uint8_t pixelFilter;
+ uint8_t pixelFilter, filterModel;
uint16_t quantizeValue;
uint32_t soundFrequency, soundBufferSize, audioInputFrequency;
} config_t;
--- a/src/pt2_diskop.c
+++ b/src/pt2_diskop.c
@@ -27,7 +27,6 @@
#include "pt2_textout.h"
#include "pt2_diskop.h"
#include "pt2_tables.h"
-#include "pt2_palette.h"
#include "pt2_module_loader.h"
#include "pt2_audio.h"
#include "pt2_sampler.h"
--- /dev/null
+++ b/src/pt2_downsample2x.c
@@ -1,0 +1,215 @@
+// for finding memory leaks in debug mode with Visual Studio
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "pt2_helpers.h" // CLAMP
+
+static double state[2];
+
+/*
+** - all-pass halfband filters (2x downsample) -
+*/
+
+static double f(const double in, double *b, const double c)
+{
+ const double x = (in - *b) * c;
+ const double out = *b + x;
+ *b = in + x;
+
+ return out;
+}
+
+static double d2x(const double *input, double *b)
+{
+ return (f(input[0], &b[0], 0.150634765625) + f(input[1], &b[1], -0.3925628662109375)) * 0.5;
+}
+
+// Warning: These can exceed original range because of undershoot/overshoot!
+
+void downsample2xDouble(double *buffer, int32_t originalLength)
+{
+ state[0] = state[1] = 0.0;
+
+ const double *input = buffer;
+ const int32_t length = originalLength / 2;
+ for (int32_t i = 0; i < length; i++, input += 2)
+ buffer[i] = d2x(input, state);
+}
+
+void downsample2xFloat(float *buffer, int32_t originalLength)
+{
+ double in[2];
+
+ state[0] = state[1] = 0.0;
+
+ const float *input = buffer;
+ const int32_t length = originalLength / 2;
+ for (int32_t i = 0; i < length; i++, input += 2)
+ {
+ in[0] = input[0];
+ in[1] = input[1];
+
+ buffer[i] = (float)d2x(in, state);
+ }
+}
+
+// Warning: These are slow and use normalization to prevent clipping from undershoot/overshoot!
+
+bool downsample2x8BitU(uint8_t *buffer, int32_t originalLength)
+{
+ state[0] = state[1] = 0.0;
+
+ double *dBuffer = (double *)malloc(originalLength * sizeof (double));
+ if (dBuffer == NULL)
+ return false;
+
+ for (int32_t i = 0; i < originalLength; i++)
+ dBuffer[i] = (buffer[i] - 128) * (1.0 / (INT8_MAX+1.0));
+
+ const double *input = dBuffer;
+ double dPeak = 0.0;
+
+ const int32_t length = originalLength / 2;
+ for (int32_t i = 0; i < length; i++, input += 2)
+ {
+ double dOut = d2x(input, state);
+ dBuffer[i] = dOut;
+
+ dOut = ABS(dOut);
+ if (dOut > dPeak)
+ dPeak = dOut;
+ }
+
+ // normalize
+
+ double dAmp = 1.0;
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+
+ for (int32_t i = 0; i < length; i++)
+ buffer[i] = (uint8_t)round(dBuffer[i] * dAmp) + 128;
+
+ free(dBuffer);
+
+ return true;
+}
+
+bool downsample2x8Bit(int8_t *buffer, int32_t originalLength)
+{
+ state[0] = state[1] = 0.0;
+
+ double *dBuffer = (double *)malloc(originalLength * sizeof (double));
+ if (dBuffer == NULL)
+ return false;
+
+ for (int32_t i = 0; i < originalLength; i++)
+ dBuffer[i] = buffer[i] * (1.0 / (INT8_MAX+1.0));
+
+ const double *input = dBuffer;
+ double dPeak = 0.0;
+
+ const int32_t length = originalLength / 2;
+ for (int32_t i = 0; i < length; i++, input += 2)
+ {
+ double dOut = d2x(input, state);
+ dBuffer[i] = dOut;
+
+ dOut = ABS(dOut);
+ if (dOut > dPeak)
+ dPeak = dOut;
+ }
+
+ // normalize
+
+ double dAmp = 1.0;
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+
+ for (int32_t i = 0; i < length; i++)
+ buffer[i] = (int8_t)round(dBuffer[i] * dAmp);
+
+ free(dBuffer);
+
+ return true;
+}
+
+bool downsample2x16Bit(int16_t *buffer, int32_t originalLength)
+{
+ state[0] = state[1] = 0.0;
+
+ double *dBuffer = (double *)malloc(originalLength * sizeof (double));
+ if (dBuffer == NULL)
+ return false;
+
+ for (int32_t i = 0; i < originalLength; i++)
+ dBuffer[i] = buffer[i] * (1.0 / (INT16_MAX+1.0));
+
+ const double *input = dBuffer;
+ double dPeak = 0.0;
+
+ const int32_t length = originalLength / 2;
+ for (int32_t i = 0; i < length; i++, input += 2)
+ {
+ double dOut = d2x(input, state);
+ dBuffer[i] = dOut;
+
+ dOut = ABS(dOut);
+ if (dOut > dPeak)
+ dPeak = dOut;
+ }
+
+ // normalize
+
+ double dAmp = 1.0;
+ if (dPeak > 0.0)
+ dAmp = INT16_MAX / dPeak;
+
+ for (int32_t i = 0; i < length; i++)
+ buffer[i] = (int16_t)round(dBuffer[i] * dAmp);
+
+ free(dBuffer);
+
+ return true;
+}
+
+bool downsample2x32Bit(int32_t *buffer, int32_t originalLength)
+{
+ state[0] = state[1] = 0.0;
+
+ double *dBuffer = (double *)malloc(originalLength * sizeof (double));
+ if (dBuffer == NULL)
+ return false;
+
+ for (int32_t i = 0; i < originalLength; i++)
+ dBuffer[i] = buffer[i] * (1.0 / (INT32_MAX+1.0));
+
+ const double *input = dBuffer;
+ double dPeak = 0.0;
+
+ const int32_t length = originalLength / 2;
+ for (int32_t i = 0; i < length; i++, input += 2)
+ {
+ double dOut = d2x(input, state);
+ dBuffer[i] = dOut;
+
+ dOut = ABS(dOut);
+ if (dOut > dPeak)
+ dPeak = dOut;
+ }
+
+ // normalize
+
+ double dAmp = 1.0;
+ if (dPeak > 0.0)
+ dAmp = INT32_MAX / dPeak;
+
+ for (int32_t i = 0; i < length; i++)
+ buffer[i] = (int32_t)round(dBuffer[i] * dAmp);
+
+ free(dBuffer);
+
+ return true;
+}
--- /dev/null
+++ b/src/pt2_downsamplers2x.h
@@ -1,0 +1,17 @@
+#pragma once
+
+#include <stdint.h>
+
+// all-pass halfband filters
+
+// Warning: These can exceed -1.0 .. 1.0 because of undershoot/overshoot!
+
+void downsample2xFloat(float *buffer, int32_t originalLength);
+void downsample2xDouble(double *buffer, int32_t originalLength);
+
+// Warning: These are slow and use normalization to prevent clipping from undershoot/overshoot!
+
+void downsample2x8Bit(int8_t *buffer, int32_t originalLength);
+void downsample2x8BitU(uint8_t *buffer, int32_t originalLength);
+void downsample2x16Bit(int16_t *buffer, int32_t originalLength);
+void downsample2x32Bit(int32_t *buffer, int32_t originalLength);
--- a/src/pt2_edit.c
+++ b/src/pt2_edit.c
@@ -18,7 +18,6 @@
#include "pt2_textout.h"
#include "pt2_tables.h"
#include "pt2_audio.h"
-#include "pt2_palette.h"
#include "pt2_diskop.h"
#include "pt2_mouse.h"
#include "pt2_sampler.h"
@@ -29,6 +28,7 @@
#include "pt2_config.h"
#include "pt2_audio.h"
#include "pt2_sync.h"
+#include "pt2_chordmaker.h"
const int8_t scancode2NoteLo[52] = // "USB usage page standard" order
{
--- a/src/pt2_header.h
+++ b/src/pt2_header.h
@@ -14,7 +14,7 @@
#include "pt2_unicode.h"
#include "pt2_palette.h"
-#define PROG_VER_STR "1.22"
+#define PROG_VER_STR "1.23"
#ifdef _WIN32
#define DIR_DELIMITER '\\'
@@ -103,7 +103,8 @@
TEMPFLAG_START = 1,
TEMPFLAG_DELAY = 2,
- FILTER_A500 = 1,
+ FILTERMODEL_A1200 = 0,
+ FILTERMODEL_A500 = 1,
FILTER_LED_ENABLED = 2,
NO_CARRY = 0,
--- a/src/pt2_helpers.c
+++ b/src/pt2_helpers.c
@@ -18,7 +18,6 @@
#include "pt2_helpers.h"
#include "pt2_header.h"
#include "pt2_tables.h"
-#include "pt2_palette.h"
#include "pt2_structs.h"
#include "pt2_config.h"
@@ -161,45 +160,4 @@
}
SDL_SetWindowTitle(video.window, titleTemp);
-}
-
-void recalcChordLength(void)
-{
- int8_t note;
- int32_t len;
- moduleSample_t *s;
-
- s = &song->samples[editor.currSample];
-
- if (editor.chordLengthMin)
- {
- note = MAX(MAX((editor.note1 == 36) ? -1 : editor.note1,
- (editor.note2 == 36) ? -1 : editor.note2),
- MAX((editor.note3 == 36) ? -1 : editor.note3,
- (editor.note4 == 36) ? -1 : editor.note4));
- }
- else
- {
- note = MIN(MIN(editor.note1, editor.note2), MIN(editor.note3, editor.note4));
- }
-
- if (note < 0 || note > 35)
- {
- editor.chordLength = 0;
- }
- else
- {
- assert(editor.tuningNote < 36);
- if (editor.tuningNote < 36)
- {
- len = (s->length * periodTable[(37 * s->fineTune) + note]) / periodTable[editor.tuningNote];
- if (len > MAX_SAMPLE_LEN)
- len = MAX_SAMPLE_LEN;
-
- editor.chordLength = len & 0xFFFE;
- }
- }
-
- if (ui.editOpScreenShown && ui.editOpScreen == 3)
- ui.updateLengthText = true;
}
--- a/src/pt2_helpers.h
+++ b/src/pt2_helpers.h
@@ -64,4 +64,3 @@
bool sampleNameIsEmpty(char *name);
bool moduleNameIsEmpty(char *name);
void updateWindowTitle(bool modified);
-void recalcChordLength(void);
--- a/src/pt2_keyboard.c
+++ b/src/pt2_keyboard.c
@@ -16,7 +16,6 @@
#include "pt2_header.h"
#include "pt2_helpers.h"
#include "pt2_visuals.h"
-#include "pt2_palette.h"
#include "pt2_diskop.h"
#include "pt2_edit.h"
#include "pt2_sampler.h"
@@ -29,6 +28,7 @@
#include "pt2_unicode.h"
#include "pt2_config.h"
#include "pt2_sampling.h"
+#include "pt2_chordmaker.h"
#if defined _WIN32 && !defined _DEBUG
extern bool windowsKeyIsDown;
@@ -718,7 +718,7 @@
}
else
{
- toggleA500Filters();
+ toggleFilterModel();
}
}
break;
@@ -3959,12 +3959,6 @@
pointerSetPreviousMode();
setPrevStatusMessage();
-
- editor.errorMsgActive = true;
- editor.errorMsgBlock = true;
- editor.errorMsgCounter = 0;
-
- setErrPointer();
}
break;
--- /dev/null
+++ b/src/pt2_ledfilter.c
@@ -1,0 +1,87 @@
+#include <stdint.h>
+#include <math.h>
+#include "pt2_rcfilter.h" // DENORMAL_OFFSET definition
+#include "pt2_ledfilter.h"
+
+/* Imperfect Amiga "LED" filter implementation. This may be further improved in the future.
+** Based upon ideas posted by mystran @ the kvraudio.com forum.
+**
+** This filter may not function correctly used outside the fixed-cutoff context here!
+*/
+
+void clearLEDFilterState(ledFilter_t *filterLED)
+{
+ filterLED->buffer[0] = 0.0; // left channel
+ filterLED->buffer[1] = 0.0;
+ filterLED->buffer[2] = 0.0; // right channel
+ filterLED->buffer[3] = 0.0;
+}
+
+static double sigmoid(double x, double coefficient)
+{
+ /* aciddose:
+ ** Coefficient from:
+ ** 0.0 to inf (linear)
+ ** -1.0 to -inf (linear)
+ */
+ return x / (x + coefficient) * (coefficient + 1.0);
+}
+
+void calcLEDFilterCoeffs(const double sr, const double hz, const double fb, ledFilter_t *filter)
+{
+ /* aciddose:
+ ** tan() may produce NaN or other bad results in some cases!
+ ** It appears to work correctly with these specific coefficients.
+ */
+
+ const double pi = 4.0 * atan(1.0); // M_PI can not be trusted
+
+ const double c = (hz < (sr / 2.0)) ? tan((pi * hz) / sr) : 1.0;
+ const double g = 1.0 / (1.0 + c);
+
+ // aciddose: dirty compensation
+ const double s = 0.5;
+ const double t = 0.5;
+ const double ic = c > t ? 1.0 / ((1.0 - s*t) + s*c) : 1.0;
+ const double cg = c * g;
+ const double fbg = 1.0 / (1.0 + fb * cg*cg);
+
+ filter->c = c;
+ filter->ci = g;
+ filter->feedback = 2.0 * sigmoid(fb, 0.5);
+ filter->bg = fbg * filter->feedback * ic;
+ filter->cg = cg;
+ filter->c2 = c * 2.0;
+}
+
+void LEDFilter(ledFilter_t *f, const double *in, double *out)
+{
+ const double in_1 = DENORMAL_OFFSET;
+ const double in_2 = DENORMAL_OFFSET;
+
+ const double c = f->c;
+ const double g = f->ci;
+ const double cg = f->cg;
+ const double bg = f->bg;
+ const double c2 = f->c2;
+
+ double *v = f->buffer;
+
+ // left channel
+ const double estimate_L = in_2 + g*(v[1] + c*(in_1 + g*(v[0] + c*in[0])));
+ const double y0_L = v[0]*g + in[0]*cg + in_1 + estimate_L * bg;
+ const double y1_L = v[1]*g + y0_L*cg + in_2;
+
+ v[0] += c2 * (in[0] - y0_L);
+ v[1] += c2 * (y0_L - y1_L);
+ out[0] = y1_L;
+
+ // right channel
+ const double estimate_R = in_2 + g*(v[3] + c*(in_1 + g*(v[2] + c*in[1])));
+ const double y0_R = v[2]*g + in[1]*cg + in_1 + estimate_R * bg;
+ const double y1_R = v[3]*g + y0_R*cg + in_2;
+
+ v[2] += c2 * (in[1] - y0_R);
+ v[3] += c2 * (y0_R - y1_R);
+ out[1] = y1_R;
+}
--- /dev/null
+++ b/src/pt2_ledfilter.h
@@ -1,0 +1,14 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef struct ledFilter_t
+{
+ double buffer[4];
+ double c, ci, feedback, bg, cg, c2;
+} ledFilter_t;
+
+void clearLEDFilterState(ledFilter_t *filterLED);
+void calcLEDFilterCoeffs(const double sr, const double hz, const double fb, ledFilter_t *filter);
+void LEDFilter(ledFilter_t *f, const double *in, double *out);
--- a/src/pt2_main.c
+++ b/src/pt2_main.c
@@ -19,7 +19,6 @@
#include <sys/stat.h>
#include "pt2_header.h"
#include "pt2_helpers.h"
-#include "pt2_palette.h"
#include "pt2_keyboard.h"
#include "pt2_textout.h"
#include "pt2_mouse.h"
@@ -428,7 +427,7 @@
if (!ui.askScreenShown && ui.introScreenShown)
{
- if (!ui.clearScreenShown && !ui.diskOpScreenShown)
+ if (!ui.clearScreenShown && !ui.diskOpScreenShown && !editor.errorMsgActive)
statusAllRight();
ui.introScreenShown = false;
@@ -509,7 +508,7 @@
editor.oldNote4 = 36;
editor.tuningVol = 32;
editor.sampleVol = 100;
- editor.tuningNote = 24;
+ editor.tuningNote = 24; // C-3
editor.metroSpeed = 4;
editor.editMoveAdd = 1;
editor.initialTempo = 125;
@@ -648,39 +647,37 @@
#ifdef _WIN32
static void disableWasapi(void)
{
- const char *audioDriver;
- int32_t i, numAudioDrivers;
-
// disable problematic WASAPI SDL2 audio driver on Windows (causes clicks/pops sometimes...)
- numAudioDrivers = SDL_GetNumAudioDrivers();
- for (i = 0; i < numAudioDrivers; i++)
+ const int32_t numAudioDrivers = SDL_GetNumAudioDrivers();
+ if (numAudioDrivers <= 1)
+ return;
+
+ // look for directsound and enable it if found
+ for (int32_t i = 0; i < numAudioDrivers; i++)
{
- audioDriver = SDL_GetAudioDriver(i);
+ const char *audioDriver = SDL_GetAudioDriver(i);
if (audioDriver != NULL && strcmp("directsound", audioDriver) == 0)
{
SDL_setenv("SDL_AUDIODRIVER", "directsound", true);
audio.rescanAudioDevicesSupported = false;
- break;
+ return;
}
}
- if (i == numAudioDrivers)
+ // directsound is not available, try winmm
+ for (int32_t i = 0; i < numAudioDrivers; i++)
{
- // directsound is not available, try winmm
- for (i = 0; i < numAudioDrivers; i++)
+ const char *audioDriver = SDL_GetAudioDriver(i);
+ if (audioDriver != NULL && strcmp("winmm", audioDriver) == 0)
{
- audioDriver = SDL_GetAudioDriver(i);
- if (audioDriver != NULL && strcmp("winmm", audioDriver) == 0)
- {
- SDL_setenv("SDL_AUDIODRIVER", "winmm", true);
- audio.rescanAudioDevicesSupported = false;
- break;
- }
+ SDL_setenv("SDL_AUDIODRIVER", "winmm", true);
+ audio.rescanAudioDevicesSupported = false;
+ return;
}
}
- // maybe we didn't find directsound or winmm, let's use wasapi after all then...
+ // we didn't find directsound or winmm, let's use wasapi after all...
}
static void makeSureDirIsProgramDir(void)
--- a/src/pt2_mouse.c
+++ b/src/pt2_mouse.c
@@ -12,7 +12,6 @@
#include "pt2_header.h"
#include "pt2_mouse.h"
#include "pt2_helpers.h"
-#include "pt2_palette.h"
#include "pt2_diskop.h"
#include "pt2_sampler.h"
#include "pt2_module_loader.h"
@@ -26,6 +25,7 @@
#include "pt2_config.h"
#include "pt2_bmp.h"
#include "pt2_sampling.h"
+#include "pt2_chordmaker.h"
/* TODO: Move irrelevant routines outta here! Disgusting design!
** Keep in mind that this was programmed in my early programming days...
@@ -2164,12 +2164,6 @@
ui.clearScreenShown = false;
setPrevStatusMessage();
pointerSetPreviousMode();
-
- editor.errorMsgActive = true;
- editor.errorMsgBlock = true;
- editor.errorMsgCounter = 0;
-
- setErrPointer();
removeClearScreen();
return true;
}
@@ -2379,7 +2373,7 @@
if (editor.errorMsgActive)
{
- if (++editor.errorMsgCounter >= (uint8_t)(VBLANK_HZ/1.15))
+ if (++editor.errorMsgCounter >= 55)
{
editor.errorMsgCounter = 0;
@@ -3268,340 +3262,31 @@
case PTB_EO_VOL_DOWN: edVolDownButton(); break;
// ----------------------------------------------------------
- // Edit Op. Screen #4
+ // Edit Op. Screen #4 (chord maker)
- case PTB_EO_DOCHORD:
- {
- ui.askScreenShown = true;
- ui.askScreenType = ASK_MAKE_CHORD;
- pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
- setStatusMessage("MAKE CHORD?", NO_CARRY);
- renderAskDialog();
- }
- break;
-
- case PTB_EO_MAJOR:
- {
- if (editor.note1 == 36)
- {
- displayErrorMsg("NO BASENOTE!");
- break;
- }
-
- editor.oldNote1 = editor.note1;
- editor.oldNote2 = editor.note2;
- editor.oldNote3 = editor.note3;
- editor.oldNote4 = editor.note4;
-
- editor.note2 = editor.note1 + 4;
- editor.note3 = editor.note1 + 7;
-
- if (editor.note2 >= 36) editor.note2 -= 12;
- if (editor.note3 >= 36) editor.note3 -= 12;
-
- editor.note4 = 36;
-
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- recalcChordLength();
- }
- break;
-
- case PTB_EO_MAJOR7:
- {
- if (editor.note1 == 36)
- {
- displayErrorMsg("NO BASENOTE!");
- break;
- }
-
- editor.oldNote1 = editor.note1;
- editor.oldNote2 = editor.note2;
- editor.oldNote3 = editor.note3;
- editor.oldNote4 = editor.note4;
-
- editor.note2 = editor.note1 + 4;
- editor.note3 = editor.note1 + 7;
- editor.note4 = editor.note1 + 11;
-
- if (editor.note2 >= 36) editor.note2 -= 12;
- if (editor.note3 >= 36) editor.note3 -= 12;
- if (editor.note4 >= 36) editor.note4 -= 12;
-
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- recalcChordLength();
- }
- break;
-
- case PTB_EO_NOTE1:
- {
- if (mouse.rightButtonPressed)
- {
- editor.note1 = 36;
- }
- else
- {
- ui.changingChordNote = 1;
- setStatusMessage("SELECT NOTE", NO_CARRY);
- pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
- }
- ui.updateNote1Text = true;
- }
- break;
-
+ case PTB_EO_DOCHORD: makeChord(); break;
+ case PTB_EO_NOTE1: selectChordNote1(); break;
+ case PTB_EO_NOTE2: selectChordNote2(); break;
+ case PTB_EO_NOTE3: selectChordNote3(); break;
+ case PTB_EO_NOTE4: selectChordNote4(); break;
case PTB_EO_NOTE1_UP: edNote1UpButton(); break;
case PTB_EO_NOTE1_DOWN: edNote1DownButton(); break;
-
- case PTB_EO_NOTE2:
- {
- if (mouse.rightButtonPressed)
- {
- editor.note2 = 36;
- }
- else
- {
- ui.changingChordNote = 2;
- setStatusMessage("SELECT NOTE", NO_CARRY);
- pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
- }
- ui.updateNote2Text = true;
- }
- break;
-
case PTB_EO_NOTE2_UP: edNote2UpButton(); break;
case PTB_EO_NOTE2_DOWN: edNote2DownButton(); break;
-
- case PTB_EO_NOTE3:
- {
- if (mouse.rightButtonPressed)
- {
- editor.note3 = 36;
- }
- else
- {
- ui.changingChordNote = 3;
- setStatusMessage("SELECT NOTE", NO_CARRY);
- pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
- }
- ui.updateNote3Text = true;
- }
- break;
-
case PTB_EO_NOTE3_UP: edNote3UpButton(); break;
case PTB_EO_NOTE3_DOWN: edNote3DownButton(); break;
-
- case PTB_EO_NOTE4:
- {
- if (mouse.rightButtonPressed)
- {
- editor.note4 = 36;
- }
- else
- {
- ui.changingChordNote = 4;
- setStatusMessage("SELECT NOTE", NO_CARRY);
- pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
- }
- ui.updateNote4Text = true;
- }
- break;
-
case PTB_EO_NOTE4_UP: edNote4UpButton(); break;
case PTB_EO_NOTE4_DOWN: edNote4DownButton(); break;
-
- case PTB_EO_RESET:
- {
- editor.note1 = 36;
- editor.note2 = 36;
- editor.note3 = 36;
- editor.note4 = 36;
-
- editor.chordLengthMin = false;
-
- ui.updateNote1Text = true;
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- recalcChordLength();
- }
- break;
-
- case PTB_EO_MINOR:
- {
- if (editor.note1 == 36)
- {
- displayErrorMsg("NO BASENOTE!");
- break;
- }
-
- editor.oldNote1 = editor.note1;
- editor.oldNote2 = editor.note2;
- editor.oldNote3 = editor.note3;
- editor.oldNote4 = editor.note4;
-
- editor.note2 = editor.note1 + 3;
- editor.note3 = editor.note1 + 7;
-
- if (editor.note2 >= 36) editor.note2 -= 12;
- if (editor.note3 >= 36) editor.note3 -= 12;
-
- editor.note4 = 36;
-
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- recalcChordLength();
- }
- break;
-
- case PTB_EO_MINOR7:
- {
- if (editor.note1 == 36)
- {
- displayErrorMsg("NO BASENOTE!");
- break;
- }
-
- editor.oldNote1 = editor.note1;
- editor.oldNote2 = editor.note2;
- editor.oldNote3 = editor.note3;
- editor.oldNote4 = editor.note4;
-
- editor.note2 = editor.note1 + 3;
- editor.note3 = editor.note1 + 7;
- editor.note4 = editor.note1 + 10;
-
- if (editor.note2 >= 36) editor.note2 -= 12;
- if (editor.note3 >= 36) editor.note3 -= 12;
- if (editor.note4 >= 36) editor.note4 -= 12;
-
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- recalcChordLength();
- }
- break;
-
- case PTB_EO_UNDO:
- {
- editor.note1 = editor.oldNote1;
- editor.note2 = editor.oldNote2;
- editor.note3 = editor.oldNote3;
- editor.note4 = editor.oldNote4;
-
- ui.updateNote1Text = true;
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- recalcChordLength();
- }
- break;
-
- case PTB_EO_SUS4:
- {
- if (editor.note1 == 36)
- {
- displayErrorMsg("NO BASENOTE!");
- break;
- }
-
- editor.oldNote1 = editor.note1;
- editor.oldNote2 = editor.note2;
- editor.oldNote3 = editor.note3;
- editor.oldNote4 = editor.note4;
-
- editor.note2 = editor.note1 + 5;
- editor.note3 = editor.note1 + 7;
-
- if (editor.note2 >= 36) editor.note2 -= 12;
- if (editor.note3 >= 36) editor.note3 -= 12;
-
- editor.note4 = 36;
-
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- recalcChordLength();
- }
- break;
-
- case PTB_EO_MAJOR6:
- {
- if (editor.note1 == 36)
- {
- displayErrorMsg("NO BASENOTE!");
- break;
- }
-
- editor.oldNote1 = editor.note1;
- editor.oldNote2 = editor.note2;
- editor.oldNote3 = editor.note3;
- editor.oldNote4 = editor.note4;
-
- editor.note2 = editor.note1 + 4;
- editor.note3 = editor.note1 + 7;
- editor.note4 = editor.note1 + 9;
-
- if (editor.note2 >= 36) editor.note2 -= 12;
- if (editor.note3 >= 36) editor.note3 -= 12;
- if (editor.note4 >= 36) editor.note4 -= 12;
-
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- recalcChordLength();
- }
- break;
-
- case PTB_EO_LENGTH:
- {
- if (song->samples[editor.currSample].loopLength == 2 && song->samples[editor.currSample].loopStart == 0)
- {
- editor.chordLengthMin = mouse.rightButtonPressed ? true : false;
- recalcChordLength();
- }
- }
- break;
-
- case PTB_EO_MINOR6:
- {
- if (editor.note1 == 36)
- {
- displayErrorMsg("NO BASENOTE!");
- break;
- }
-
- editor.oldNote1 = editor.note1;
- editor.oldNote2 = editor.note2;
- editor.oldNote3 = editor.note3;
- editor.oldNote4 = editor.note4;
-
- editor.note2 = editor.note1 + 3;
- editor.note3 = editor.note1 + 7;
- editor.note4 = editor.note1 + 9;
-
- if (editor.note2 >= 36) editor.note2 -= 12;
- if (editor.note3 >= 36) editor.note3 -= 12;
- if (editor.note4 >= 36) editor.note4 -= 12;
-
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- recalcChordLength();
- }
- break;
+ case PTB_EO_RESET: resetChord(); break;
+ case PTB_EO_UNDO: undoChord(); break;
+ case PTB_EO_LENGTH: toggleChordLength(); break;
+ case PTB_EO_MAJOR: setChordMajor(); break;
+ case PTB_EO_MINOR: setChordMinor(); break;
+ case PTB_EO_SUS4: setChordSus4(); break;
+ case PTB_EO_MAJOR7: setChordMajor7(); break;
+ case PTB_EO_MINOR7: setChordMinor7(); break;
+ case PTB_EO_MAJOR6: setChordMajor6(); break;
+ case PTB_EO_MINOR6: setChordMinor6(); break;
// ----------------------------------------------------------
case PTB_ABOUT:
@@ -4646,10 +4331,6 @@
removeClearScreen();
setPrevStatusMessage();
pointerSetPreviousMode();
- editor.errorMsgActive = true;
- editor.errorMsgBlock = true;
- editor.errorMsgCounter = 0;
- setErrPointer();
}
break;
--- a/src/pt2_pat2smp.c
+++ b/src/pt2_pat2smp.c
@@ -12,6 +12,9 @@
#include "pt2_audio.h"
#include "pt2_sampler.h"
#include "pt2_textout.h"
+#include "pt2_rcfilter.h"
+#include "pt2_pat2smp.h"
+#include "pt2_downsamplers2x.h"
bool intMusic(void); // pt_modplayer.c
void storeTempVariables(void); // pt_modplayer.c
@@ -28,8 +31,8 @@
return;
}
- editor.pat2SmpBuf = (int16_t *)malloc(MAX_SAMPLE_LEN * sizeof (int16_t));
- if (editor.pat2SmpBuf == NULL)
+ editor.dPat2SmpBuf = (double *)malloc((MAX_SAMPLE_LEN*2) * sizeof (double));
+ if (editor.dPat2SmpBuf == NULL)
{
statusOutOfMemory();
return;
@@ -69,36 +72,54 @@
editor.isSMPRendering = false;
resetSong();
+ int32_t renderLength = editor.pat2SmpPos;
+
+ s = &song->samples[editor.currSample];
+
// set back old row
song->currRow = song->row = oldRow;
- normalize16bitSigned(editor.pat2SmpBuf, MIN(editor.pat2SmpPos, MAX_SAMPLE_LEN));
+ // downsample oversampled buffer, normalize and quantize to 8-bit
- s = &song->samples[editor.currSample];
+ downsample2xDouble(editor.dPat2SmpBuf, renderLength);
+ renderLength /= 2;
- // quantize to 8-bit
- for (int32_t i = 0; i < editor.pat2SmpPos; i++)
- song->sampleData[s->offset+i] = editor.pat2SmpBuf[i] >> 8;
+ double dAmp = 1.0;
+ const double dPeak = getDoublePeak(editor.dPat2SmpBuf, renderLength);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
- // clear the rest of the sample
- if (editor.pat2SmpPos < MAX_SAMPLE_LEN)
- memset(&song->sampleData[s->offset+editor.pat2SmpPos], 0, MAX_SAMPLE_LEN - editor.pat2SmpPos);
+ double dVol = 64.0 * dPeak;
+ if (dVol > 64.0)
+ dVol = 64.0;
- free(editor.pat2SmpBuf);
+ int8_t *smpPtr = &song->sampleData[s->offset];
+ for (int32_t i = 0; i < renderLength; i++)
+ {
+ const int32_t smp = (const int32_t)round(editor.dPat2SmpBuf[i] * dAmp);
+ assert(smp >= -128 && smp <= 127); // shouldn't happen according to dAmp (but just in case)
+ smpPtr[i] = (int8_t)smp;
+ }
+ free(editor.dPat2SmpBuf);
+
+ // clear the rest of the sample (if not full)
+ if (renderLength < MAX_SAMPLE_LEN)
+ memset(&song->sampleData[s->offset+renderLength], 0, MAX_SAMPLE_LEN - renderLength);
+
if (editor.pat2SmpHQ)
{
- strcpy(s->text, "pat2smp (a-3 tune:+4)");
+ strcpy(s->text, "pat2smp(a-3 ftune:+4)");
s->fineTune = 4;
}
else
{
- strcpy(s->text, "pat2smp (f-3 tune:+1)");
- s->fineTune = 1;
+ strcpy(s->text, "pat2smp(e-3 ftune: 0)");
+ s->fineTune = 0;
}
- s->length = (uint16_t)editor.pat2SmpPos;
- s->volume = 64;
+ s->length = (uint16_t)renderLength;
+ s->volume = (int8_t)round(dVol);
s->loopStart = 0;
s->loopLength = 2;
--- a/src/pt2_pat2smp.h
+++ b/src/pt2_pat2smp.h
@@ -2,10 +2,12 @@
#include "pt2_header.h"
+// we do 2x oversampling for BLEP synthesis to work right on all ProTracker pitches
+
#define PAT2SMP_HI_PERIOD 124 /* A-3 finetune +4, 28603.99Hz */
-#define PAT2SMP_LO_PERIOD 160 /* F-3 finetune +1, 22168.09Hz */
+#define PAT2SMP_LO_PERIOD 170 /* E-3 finetune 0, 20864.08Hz */
-#define PAT2SMP_HI_FREQ (PAULA_PAL_CLK / (double)PAT2SMP_HI_PERIOD)
-#define PAT2SMP_LO_FREQ (PAULA_PAL_CLK / (double)PAT2SMP_LO_PERIOD)
+#define PAT2SMP_HI_FREQ (PAULA_PAL_CLK / (PAT2SMP_HI_PERIOD / 2.0))
+#define PAT2SMP_LO_FREQ (PAULA_PAL_CLK / (PAT2SMP_LO_PERIOD / 2.0))
void doPat2Smp(void);
--- a/src/pt2_pattern_viewer.c
+++ b/src/pt2_pattern_viewer.c
@@ -1,7 +1,6 @@
#include <stdint.h>
#include <stdbool.h>
#include "pt2_header.h"
-#include "pt2_palette.h"
#include "pt2_tables.h"
#include "pt2_textout.h"
#include "pt2_structs.h"
--- /dev/null
+++ b/src/pt2_rcfilter.c
@@ -1,0 +1,70 @@
+#include <stdint.h>
+#include <math.h>
+#include "pt2_rcfilter.h"
+
+void calcRCFilterCoeffs(double dSr, double dHz, rcFilter_t *f)
+{
+ const double pi = 4.0 * atan(1.0); // M_PI can not be trusted
+
+ const double c = (dHz < (dSr / 2.0)) ? tan((pi * dHz) / dSr) : 1.0;
+ f->c = c;
+ f->c2 = f->c * 2.0;
+ f->g = 1.0 / (1.0 + f->c);
+ f->cg = f->c * f->g;
+}
+
+void clearRCFilterState(rcFilter_t *f)
+{
+ f->buffer[0] = 0.0; // left channel
+ f->buffer[1] = 0.0; // right channel
+}
+
+// aciddose: input 0 is resistor side of capacitor (low-pass), input 1 is reference side (high-pass)
+static inline double getLowpassOutput(rcFilter_t *f, const double input_0, const double input_1, const double buffer)
+{
+ double dOutput = DENORMAL_OFFSET;
+
+ dOutput += buffer * f->g + input_0 * f->cg + input_1 * (1.0 - f->cg);
+
+ return dOutput;
+}
+
+void RCLowPassFilterStereo(rcFilter_t *f, const double *in, double *out)
+{
+ double output;
+
+ // left channel RC low-pass
+ output = getLowpassOutput(f, in[0], 0.0, f->buffer[0]);
+ f->buffer[0] += (in[0] - output) * f->c2;
+ out[0] = output;
+
+ // right channel RC low-pass
+ output = getLowpassOutput(f, in[1], 0.0, f->buffer[1]);
+ f->buffer[1] += (in[1] - output) * f->c2;
+ out[1] = output;
+}
+
+void RCHighPassFilterStereo(rcFilter_t *f, const double *in, double *out)
+{
+ double low[2];
+
+ RCLowPassFilterStereo(f, in, low);
+
+ out[0] = in[0] - low[0]; // left channel high-pass
+ out[1] = in[1] - low[1]; // right channel high-pass
+}
+
+void RCLowPassFilter(rcFilter_t *f, const double in, double *out)
+{
+ double output = getLowpassOutput(f, in, 0.0, f->buffer[0]);
+ f->buffer[0] += (in - output) * f->c2;
+ *out = output;
+}
+
+void RCHighPassFilter(rcFilter_t *f, const double in, double *out)
+{
+ double low;
+
+ RCLowPassFilter(f, in, &low);
+ *out = in - low; // high-pass
+}
--- /dev/null
+++ b/src/pt2_rcfilter.h
@@ -1,0 +1,21 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+// adding this prevents denormalized numbers, which is slow
+#define DENORMAL_OFFSET 1e-15
+
+typedef struct rcFilter_t
+{
+ double buffer[2];
+ double c, c2, g, cg;
+} rcFilter_t;
+
+void calcRCFilterCoeffs(const double sr, const double hz, rcFilter_t *f);
+void clearRCFilterState(rcFilter_t *f);
+void RCLowPassFilterStereo(rcFilter_t *f, const double *in, double *out);
+void RCHighPassFilterStereo(rcFilter_t *f, const double *in, double *out);
+void RCLowPassFilter(rcFilter_t *f, const double in, double *out);
+void RCHighPassFilter(rcFilter_t *f, const double in, double *out);
+
--- a/src/pt2_replayer.c
+++ b/src/pt2_replayer.c
@@ -1,4 +1,4 @@
-// C port of ProTracker 2.3D's replayer by 8bitbubsy, slightly modified.
+// C port of ProTracker 2.3D's replayer (with some modifications, but still accurate)
// for finding memory leaks in debug mode with Visual Studio
#if defined _DEBUG && defined _MSC_VER
@@ -13,7 +13,6 @@
#include "pt2_header.h"
#include "pt2_audio.h"
#include "pt2_helpers.h"
-#include "pt2_palette.h"
#include "pt2_tables.h"
#include "pt2_module_loader.h"
#include "pt2_config.h"
@@ -37,7 +36,7 @@
int8_t *allocMemForAllSamples(void)
{
- /* Allocate memoru for all sample data blocks.
+ /* Allocate memory for all sample data blocks.
**
** We need three extra sample slots:
** The 1st is extra safety padding since setting a Paula length of 0
--- a/src/pt2_sample_loader.c
+++ b/src/pt2_sample_loader.c
@@ -18,13 +18,8 @@
#include "pt2_unicode.h"
#include "pt2_config.h"
#include "pt2_sampling.h"
+#include "pt2_downsamplers2x.h"
-/* TODO: Get a low-pass filter with a steeper slope!
-** A 6db/oct filter may not be very suitable for filtering out frequencies above nyquist,
-** before 2x downsampling.
-*/
-#define DOWNSAMPLE_CUTOFF_FACTOR 4.0
-
enum
{
WAV_FORMAT_PCM = 0x0001,
@@ -31,165 +26,37 @@
WAV_FORMAT_IEEE_FLOAT = 0x0003
};
-static bool loadedFileWasAIFF;
+enum
+{
+ SAMPLE_IFF = 0,
+ SAMPLE_AIFF = 1,
+ SAMPLE_WAV = 2
+};
+
+static int8_t loadedSampleType;
+
static bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
-static bool loadIFFSample(UNICHAR *fileName, char *entryName);
+static bool loadIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
static bool loadRAWSample(UNICHAR *fileName, char *entryName);
static bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
-static bool lowPassSample8Bit(int8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+void extLoadWAVOrAIFFSampleCallback(bool downsample)
{
- rcFilter_t filter;
-
- if (buffer == NULL || length == 0 || cutoff == 0.0)
- return false;
-
- calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
- clearRCFilterState(&filter);
-
- for (int32_t i = 0; i < length; i++)
+ switch (loadedSampleType)
{
- int32_t sample;
- double dSample;
-
- RCLowPassFilterMono(&filter, buffer[i], &dSample);
- sample = (int32_t)dSample;
-
- buffer[i] = (int8_t)CLAMP(sample, INT8_MIN, INT8_MAX);
+ case SAMPLE_IFF: loadIFFSample(editor.fileNameTmpU, editor.entryNameTmp, downsample); break;
+ case SAMPLE_AIFF: loadAIFFSample(editor.fileNameTmpU, editor.entryNameTmp, downsample); break;
+ case SAMPLE_WAV: loadWAVSample(editor.fileNameTmpU, editor.entryNameTmp, downsample); break;
+ default: break;
}
-
- return true;
}
-static bool lowPassSample8BitUnsigned(uint8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
- rcFilter_t filter;
-
- if (buffer == NULL || length == 0 || cutoff == 0.0)
- return false;
-
- calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
- clearRCFilterState(&filter);
-
- for (int32_t i = 0; i < length; i++)
- {
- int32_t sample;
- double dSample;
-
- RCLowPassFilterMono(&filter, buffer[i] - 128, &dSample);
- sample = (int32_t)dSample;
-
- sample = CLAMP(sample, INT8_MIN, INT8_MAX);
- buffer[i] = (uint8_t)(sample + 128);
- }
-
- return true;
-}
-
-static bool lowPassSample16Bit(int16_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
- rcFilter_t filter;
-
- if (buffer == NULL || length == 0 || cutoff == 0.0)
- return false;
-
- calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
- clearRCFilterState(&filter);
-
- for (int32_t i = 0; i < length; i++)
- {
- int32_t sample;
- double dSample;
-
- RCLowPassFilterMono(&filter, buffer[i], &dSample);
- sample = (int32_t)dSample;
-
- buffer[i] = (int16_t)CLAMP(sample, INT16_MIN, INT16_MAX);
- }
-
- return true;
-}
-
-static bool lowPassSample32Bit(int32_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
- rcFilter_t filter;
-
- if (buffer == NULL || length == 0 || cutoff == 0.0)
- return false;
-
- calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
- clearRCFilterState(&filter);
-
- for (int32_t i = 0; i < length; i++)
- {
- int64_t sample;
- double dSample;
-
- RCLowPassFilterMono(&filter, buffer[i], &dSample);
- sample = (int32_t)dSample;
-
- buffer[i] = (int32_t)CLAMP(sample, INT32_MIN, INT32_MAX);
- }
-
- return true;
-}
-
-static bool lowPassSampleFloat(float *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
- rcFilter_t filter;
-
- if (buffer == NULL || length == 0 || cutoff == 0.0)
- return false;
-
- calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
- clearRCFilterState(&filter);
-
- for (int32_t i = 0; i < length; i++)
- {
- double dSample;
-
- RCLowPassFilterMono(&filter, buffer[i], &dSample);
- buffer[i] = (float)dSample;
- }
-
- return true;
-}
-
-static bool lowPassSampleDouble(double *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
- rcFilter_t filter;
-
- if (buffer == NULL || length == 0 || cutoff == 0.0)
- return false;
-
- calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
- clearRCFilterState(&filter);
-
- for (int32_t i = 0; i < length; i++)
- {
- double dSample;
- RCLowPassFilterMono(&filter, buffer[i], &dSample);
-
- buffer[i] = dSample;
- }
-
- return true;
-}
-
-void extLoadWAVOrAIFFSampleCallback(bool downsample)
-{
- if (loadedFileWasAIFF)
- loadAIFFSample(editor.fileNameTmpU, editor.entryNameTmp, downsample);
- else
- loadWAVSample(editor.fileNameTmpU, editor.entryNameTmp, downsample);
-}
-
bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling)
{
bool wavSampleNameFound;
uint8_t *audioDataU8;
- int16_t *audioDataS16, tempVol, smp16;
+ int16_t *audioDataS16, tempVol;
uint16_t audioFormat, numChannels, bitsPerSample;
int32_t *audioDataS32, smp32;
uint32_t *audioDataU32, i, nameLen, chunkID, chunkSize;
@@ -196,12 +63,12 @@
uint32_t sampleLength, sampleRate, filesize, loopFlags;
uint32_t loopStart, loopEnd, dataPtr, dataLen, fmtPtr, endOfChunk, bytesRead;
uint32_t fmtLen, inamPtr, inamLen, smplPtr, smplLen, xtraPtr, xtraLen;
- float *fAudioDataFloat, fSmp;
- double *dAudioDataDouble, dSmp;
+ float *fAudioDataFloat;
+ double *dAudioDataDouble;
FILE *f;
moduleSample_t *s;
- loadedFileWasAIFF = false;
+ loadedSampleType = SAMPLE_WAV;
// zero out chunk pointers and lengths
fmtPtr = 0; fmtLen = 0;
@@ -389,6 +256,8 @@
// ---- READ SAMPLE DATA ----
fseek(f, dataPtr, SEEK_SET);
+ int8_t *smpPtr = &song->sampleData[editor.currSample * MAX_SAMPLE_LEN];
+
if (bitsPerSample == 8) // 8-BIT INTEGER SAMPLE
{
if (sampleLength > MAX_SAMPLE_LEN*4)
@@ -417,21 +286,17 @@
sampleLength >>= 1;
for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
{
- smp16 = (audioDataU8[(i << 1) + 0] - 128) + (audioDataU8[(i << 1) + 1] - 128);
- smp16 = 128 + (smp16 >> 1);
- audioDataU8[i] = (uint8_t)smp16;
+ smp32 = (audioDataU8[(i << 1) + 0] - 128) + (audioDataU8[(i << 1) + 1] - 128);
+ smp32 = 128 + (smp32 >> 1);
+ audioDataU8[i] = (uint8_t)smp32;
}
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSample8BitUnsigned(audioDataU8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2x8BitU(audioDataU8, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- audioDataU8[i] = audioDataU8[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
@@ -438,13 +303,8 @@
sampleLength = MAX_SAMPLE_LEN;
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
- {
- if (i <= (sampleLength & 0xFFFFFFFE))
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataU8[i] - 128;
- else
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
- }
+ for (i = 0; i < sampleLength; i++)
+ smpPtr[i] = audioDataU8[i] - 128;
free(audioDataU8);
}
@@ -476,35 +336,37 @@
{
sampleLength >>= 1;
for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
- {
- smp32 = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
- audioDataS16[i] = (int16_t)smp32;
- }
+ audioDataS16[i] = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;;
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2x16Bit(audioDataS16, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- audioDataS16[i] = audioDataS16[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
sampleLength = MAX_SAMPLE_LEN;
- normalize16bitSigned(audioDataS16, sampleLength);
+ double dAmp = 1.0;
+ if (forceDownSampling) // we already normalized
+ {
+ dAmp = INT8_MAX / (double)INT16_MAX;
+ }
+ else
+ {
+ const double dPeak = get16BitPeak(audioDataS16, sampleLength);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+ }
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
+ for (i = 0; i < sampleLength; i++)
{
- if (i <= (sampleLength & 0xFFFFFFFE))
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8;
- else
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+ smp32 = (int32_t)round(audioDataS16[i] * dAmp);
+ assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+ smpPtr[i] = (int8_t)smp32;
}
free(audioDataS16);
@@ -543,29 +405,34 @@
}
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2x32Bit(audioDataS32, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- audioDataS32[i] = audioDataS32[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
sampleLength = MAX_SAMPLE_LEN;
- normalize32bitSigned(audioDataS32, sampleLength);
+ double dAmp = 1.0;
+ if (forceDownSampling) // we already normalized
+ {
+ dAmp = INT8_MAX / (double)INT32_MAX;
+ }
+ else
+ {
+ const double dPeak = get32BitPeak(audioDataS32, sampleLength);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+ }
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
+ for (i = 0; i < sampleLength; i++)
{
- if (i <= (sampleLength & 0xFFFFFFFE))
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
- else
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+ smp32 = (int32_t)round(audioDataS32[i] * dAmp);
+ assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+ smpPtr[i] = (int8_t)smp32;
}
free(audioDataS32);
@@ -604,29 +471,34 @@
}
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2x32Bit(audioDataS32, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- audioDataS32[i] = audioDataS32[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
sampleLength = MAX_SAMPLE_LEN;
- normalize32bitSigned(audioDataS32, sampleLength);
+ double dAmp = 1.0;
+ if (forceDownSampling) // we already normalized
+ {
+ dAmp = INT8_MAX / (double)INT32_MAX;
+ }
+ else
+ {
+ const double dPeak = get32BitPeak(audioDataS32, sampleLength);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+ }
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
+ for (i = 0; i < sampleLength; i++)
{
- if (i <= (sampleLength & 0xFFFFFFFE))
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
- else
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+ smp32 = (int32_t)round(audioDataS32[i] * dAmp);
+ assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+ smpPtr[i] = (int8_t)smp32;
}
free(audioDataS32);
@@ -661,41 +533,30 @@
{
sampleLength >>= 1;
for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
- {
- fSmp = (fAudioDataFloat[(i * 2) + 0] + fAudioDataFloat[(i * 2) + 1]) * 0.5f;
- fAudioDataFloat[i] = fSmp;
- }
+ fAudioDataFloat[i] = (fAudioDataFloat[(i * 2) + 0] + fAudioDataFloat[(i * 2) + 1]) * 0.5f;
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSampleFloat(fAudioDataFloat, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2xFloat(fAudioDataFloat, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- fAudioDataFloat[i] = fAudioDataFloat[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
sampleLength = MAX_SAMPLE_LEN;
- normalize8bitFloatSigned(fAudioDataFloat, sampleLength);
+ float fAmp = 1.0f;
+ const float fPeak = getFloatPeak(fAudioDataFloat, sampleLength);
+ if (fPeak > 0.0f)
+ fAmp = INT8_MAX / fPeak;
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
+ for (i = 0; i < sampleLength; i++)
{
- if (i <= (sampleLength & 0xFFFFFFFE))
- {
- smp32 = (int32_t)fAudioDataFloat[i];
- CLAMP8(smp32);
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32;
- }
- else
- {
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
- }
+ smp32 = (int32_t)roundf(fAudioDataFloat[i] * fAmp);
+ assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+ smpPtr[i] = (int8_t)smp32;
}
free(audioDataU32);
@@ -730,46 +591,38 @@
{
sampleLength >>= 1;
for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
- {
- dSmp = (dAudioDataDouble[(i * 2) + 0] + dAudioDataDouble[(i * 2) + 1]) * 0.5;
- dAudioDataDouble[i] = dSmp;
- }
+ dAudioDataDouble[i] = (dAudioDataDouble[(i * 2) + 0] + dAudioDataDouble[(i * 2) + 1]) * 0.5;
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSampleDouble(dAudioDataDouble, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2xDouble(dAudioDataDouble, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- dAudioDataDouble[i] = dAudioDataDouble[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
sampleLength = MAX_SAMPLE_LEN;
- normalize8bitDoubleSigned(dAudioDataDouble, sampleLength);
+ double dAmp = 1.0;
+ const double dPeak = getDoublePeak(dAudioDataDouble, sampleLength);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
+ for (i = 0; i < sampleLength; i++)
{
- if (i <= (sampleLength & 0xFFFFFFFE))
- {
- smp32 = (int32_t)dAudioDataDouble[i];
- CLAMP8(smp32);
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32;
- }
- else
- {
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
- }
+ smp32 = (int32_t)round(dAudioDataDouble[i] * dAmp);
+ assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+ smpPtr[i] = (int8_t)smp32;
}
free(audioDataU32);
}
+ if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
+ memset(&song->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
+
// set sample length
if (sampleLength & 1)
{
@@ -894,13 +747,14 @@
return true;
}
-bool loadIFFSample(UNICHAR *fileName, char *entryName)
+bool loadIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling)
{
bool nameFound, is16Bit;
char tmpCharBuf[23];
int8_t *sampleData;
- int16_t sample16, *ptr16;
- int32_t filesize;
+ int16_t *ptr16;
+ int32_t filesize, smp32;
+ uint16_t sampleRate;
uint32_t i, sampleLength, sampleLoopStart, sampleLoopLength;
uint32_t sampleVolume, blockName, blockSize;
uint32_t vhdrPtr, vhdrLen, bodyPtr, bodyLen, namePtr, nameLen;
@@ -907,6 +761,8 @@
FILE *f;
moduleSample_t *s;
+ loadedSampleType = SAMPLE_IFF;
+
s = &song->samples[editor.currSample];
vhdrPtr = 0; vhdrLen = 0;
@@ -913,6 +769,15 @@
bodyPtr = 0; bodyLen = 0;
namePtr = 0; nameLen = 0;
+ if (forceDownSampling == -1)
+ {
+ // these two *must* be fully wiped, for outputting reasons
+ memset(editor.fileNameTmpU, 0, PATH_MAX);
+ memset(editor.entryNameTmp, 0, PATH_MAX);
+ UNICHAR_STRCPY(editor.fileNameTmpU, fileName);
+ strcpy(editor.entryNameTmp, entryName);
+ }
+
f = UNICHAR_FOPEN(fileName, "rb");
if (f == NULL)
{
@@ -991,9 +856,10 @@
fseek(f, vhdrPtr, SEEK_SET);
fread(&sampleLoopStart, 4, 1, f); sampleLoopStart = SWAP32(sampleLoopStart);
fread(&sampleLoopLength, 4, 1, f); sampleLoopLength = SWAP32(sampleLoopLength);
+ fseek(f, 4, SEEK_CUR);
+ fread(&sampleRate, 2, 1, f); sampleRate = SWAP16(sampleRate);
+ fseek(f, 1, SEEK_CUR);
- fseek(f, 4 + 2 + 1, SEEK_CUR);
-
if (fgetc(f) != 0) // sample type
{
fclose(f);
@@ -1010,16 +876,6 @@
sampleVolume = 64;
sampleLength = bodyLen;
- if (is16Bit)
- {
- if (sampleLength > MAX_SAMPLE_LEN*2)
- sampleLength = MAX_SAMPLE_LEN*2;
- }
- else
- {
- if (sampleLength > MAX_SAMPLE_LEN)
- sampleLength = MAX_SAMPLE_LEN;
- }
if (sampleLength == 0)
{
@@ -1028,6 +884,30 @@
return false;
}
+ if (sampleRate > 22050)
+ {
+ if (forceDownSampling == -1)
+ {
+ showDownsampleAskDialog();
+ fclose(f);
+ return true;
+ }
+ }
+ else
+ {
+ forceDownSampling = false;
+ }
+
+ uint32_t maxSampleLength = MAX_SAMPLE_LEN;
+ if (is16Bit)
+ maxSampleLength *= 2;
+
+ if (forceDownSampling)
+ maxSampleLength *= 2;
+
+ if (sampleLength > maxSampleLength)
+ sampleLength = maxSampleLength;
+
sampleData = (int8_t *)malloc(sampleLength);
if (sampleData == NULL)
{
@@ -1043,13 +923,82 @@
sampleLoopLength >>= 1;
}
- sampleLength &= 0xFFFFFFFE;
+ if (forceDownSampling)
+ {
+ sampleLoopStart >>= 1;
+ sampleLoopLength >>= 1;
+ }
+
+ turnOffVoices();
+
+ fseek(f, bodyPtr, SEEK_SET);
+ if (is16Bit) // FT2-specific 16SV format (little-endian samples)
+ {
+ fread(sampleData, 1, sampleLength << 1, f);
+ ptr16 = (int16_t *)sampleData;
+
+ // 2x downsampling
+ if (forceDownSampling)
+ {
+ downsample2x16Bit(ptr16, sampleLength);
+ sampleLength >>= 1;
+ }
+
+ if (sampleLength > MAX_SAMPLE_LEN)
+ sampleLength = MAX_SAMPLE_LEN;
+
+ double dAmp = 1.0;
+ if (forceDownSampling) // we already normalized
+ {
+ dAmp = INT8_MAX / (double)INT16_MAX;
+ }
+ else
+ {
+ const double dPeak = get16BitPeak(ptr16, sampleLength);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+ }
+
+ int8_t *smpPtr = &song->sampleData[s->offset];
+ for (i = 0; i < sampleLength; i++)
+ {
+ smp32 = (int32_t)round(ptr16[i] * dAmp);
+ assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+ smpPtr[i] = (int8_t)smp32;
+ }
+ }
+ else
+ {
+ fread(sampleData, 1, sampleLength, f);
+
+ // 2x downsampling
+ if (forceDownSampling)
+ {
+ downsample2x8Bit(sampleData, sampleLength);
+ sampleLength >>= 1;
+ }
+
+ if (sampleLength > MAX_SAMPLE_LEN)
+ sampleLength = MAX_SAMPLE_LEN;
+
+ memcpy(&song->sampleData[s->offset], sampleData, sampleLength);
+ }
+
+ free(sampleData);
+
+ if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
+ memset(&song->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
+
+ // set sample length
+ if (sampleLength & 1)
+ {
+ if (++sampleLength > MAX_SAMPLE_LEN)
+ sampleLength = MAX_SAMPLE_LEN;
+ }
+
sampleLoopStart &= 0xFFFFFFFE;
sampleLoopLength &= 0xFFFFFFFE;
- if (sampleLength > MAX_SAMPLE_LEN)
- sampleLength = MAX_SAMPLE_LEN;
-
if (sampleLoopLength < 2)
{
sampleLoopStart = 0;
@@ -1056,9 +1005,9 @@
sampleLoopLength = 2;
}
- if (sampleLoopStart >= MAX_SAMPLE_LEN || sampleLoopLength > MAX_SAMPLE_LEN)
+ if (sampleLoopStart >= sampleLength || sampleLoopLength > sampleLength)
{
- sampleLoopStart= 0;
+ sampleLoopStart = 0;
sampleLoopLength = 2;
}
@@ -1074,31 +1023,6 @@
sampleLoopLength = 2;
}
- turnOffVoices();
-
- fseek(f, bodyPtr, SEEK_SET);
- if (is16Bit) // FT2 specific 16SV format (little-endian samples)
- {
- fread(sampleData, 1, sampleLength << 1, f);
-
- ptr16 = (int16_t *)sampleData;
- for (i = 0; i < sampleLength; i++)
- {
- sample16 = ptr16[i];
- song->sampleData[s->offset+i] = sample16 >> 8;
- }
- }
- else
- {
- fread(sampleData, 1, sampleLength, f);
- memcpy(&song->sampleData[s->offset], sampleData, sampleLength);
- }
-
- if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
- memset(&song->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
-
- free(sampleData);
-
// set sample attributes
s->volume = (int8_t)sampleVolume;
s->fineTune = 0;
@@ -1253,7 +1177,7 @@
char compType[4];
int8_t *audioDataS8;
uint8_t *audioDataU8, sampleRateBytes[10];
- int16_t *audioDataS16, smp16;
+ int16_t *audioDataS16;
uint16_t bitDepth, numChannels;
int32_t filesize, *audioDataS32, smp32;
uint32_t nameLen, i, offset, sampleRate, sampleLength, blockName, blockSize;
@@ -1262,7 +1186,7 @@
moduleSample_t *s;
unsigned8bit = false;
- loadedFileWasAIFF = true;
+ loadedSampleType = SAMPLE_AIFF;
if (forceDownSampling == -1)
{
@@ -1412,6 +1336,8 @@
forceDownSampling = false;
}
+ int8_t *smpPtr = &song->sampleData[editor.currSample * MAX_SAMPLE_LEN];
+
if (bitDepth == 8) // 8-BIT INTEGER SAMPLE
{
if (sampleLength > MAX_SAMPLE_LEN*4)
@@ -1445,21 +1371,14 @@
{
sampleLength >>= 1;
for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
- {
- smp16 = (audioDataS8[(i * 2) + 0] + audioDataS8[(i * 2) + 1]) >> 1;
- audioDataS8[i] = (uint8_t)smp16;
- }
+ audioDataS8[i] = (audioDataS8[(i * 2) + 0] + audioDataS8[(i * 2) + 1]) >> 1;;
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSample8Bit(audioDataS8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2x8Bit(audioDataS8, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- audioDataS8[i] = audioDataS8[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
@@ -1466,13 +1385,8 @@
sampleLength = MAX_SAMPLE_LEN;
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
- {
- if (i <= (sampleLength & 0xFFFFFFFE))
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS8[i];
- else
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
- }
+ for (i = 0; i < sampleLength; i++)
+ smpPtr[i] = audioDataS8[i];
free(audioDataS8);
}
@@ -1508,35 +1422,37 @@
{
sampleLength >>= 1;
for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
- {
- smp32 = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
- audioDataS16[i] = (int16_t)(smp32);
- }
+ audioDataS16[i] = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2x16Bit(audioDataS16, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- audioDataS16[i] = audioDataS16[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
sampleLength = MAX_SAMPLE_LEN;
- normalize16bitSigned(audioDataS16, sampleLength);
+ double dAmp = 1.0;
+ if (forceDownSampling) // we already normalized
+ {
+ dAmp = INT8_MAX / (double)INT16_MAX;
+ }
+ else
+ {
+ const double dPeak = get16BitPeak(audioDataS16, sampleLength);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+ }
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
+ for (i = 0; i < sampleLength; i++)
{
- if (i <= (sampleLength & 0xFFFFFFFE))
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8;
- else
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+ smp32 = (int32_t)round(audioDataS16[i] * dAmp);
+ assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+ smpPtr[i] = (int8_t)smp32;
}
free(audioDataS16);
@@ -1583,29 +1499,33 @@
}
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2x32Bit(audioDataS32, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- audioDataS32[i] = audioDataS32[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
sampleLength = MAX_SAMPLE_LEN;
- normalize32bitSigned(audioDataS32, sampleLength);
+ double dAmp = 1.0;
+ if (forceDownSampling) // we already normalized
+ {
+ dAmp = INT8_MAX / (double)INT32_MAX;
+ }
+ else
+ {
+ const double dPeak = get32BitPeak(audioDataS32, sampleLength);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+ }
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
+ for (i = 0; i < sampleLength; i++)
{
- if (i <= (sampleLength & 0xFFFFFFFE))
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
- else
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+ smp32 = (int32_t)round(audioDataS32[i] * dAmp);
+ smpPtr[i] = (int8_t)smp32;
}
free(audioDataS32);
@@ -1648,34 +1568,41 @@
}
}
- // 2x downsampling - remove every other sample (if needed)
+ // 2x downsampling
if (forceDownSampling)
{
- if (config.sampleLowpass)
- lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+ downsample2x32Bit(audioDataS32, sampleLength);
sampleLength >>= 1;
- for (i = 1; i < sampleLength; i++)
- audioDataS32[i] = audioDataS32[i << 1];
}
if (sampleLength > MAX_SAMPLE_LEN)
sampleLength = MAX_SAMPLE_LEN;
- normalize32bitSigned(audioDataS32, sampleLength);
+ double dAmp = 1.0;
+ if (forceDownSampling) // we already normalized
+ {
+ dAmp = INT8_MAX / (double)INT32_MAX;
+ }
+ else
+ {
+ const double dPeak = get32BitPeak(audioDataS32, sampleLength);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+ }
turnOffVoices();
- for (i = 0; i < MAX_SAMPLE_LEN; i++)
+ for (i = 0; i < sampleLength; i++)
{
- if (i <= (sampleLength & 0xFFFFFFFE))
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
- else
- song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+ smp32 = (int32_t)round(audioDataS32[i] * dAmp);
+ smpPtr[i] = (int8_t)smp32;
}
free(audioDataS32);
}
+ if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
+ memset(&song->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
+
// set sample length
if (sampleLength & 1)
{
@@ -1749,6 +1676,16 @@
{
fread(&ID, 4, 1, f);
+ /* Reject FLAC files, since they are not supported.
+ ** This is a dumb test since any sample *could* start
+ ** with "fLaC", but in 99.999% of cases it won't happen.
+ */
+ if (ID == 0x43614C66) // "fLaC"
+ {
+ displayErrorMsg("NOT SUPPORTED !");
+ return false;
+ }
+
// check if it's actually a WAV sample
if (ID == 0x46464952) // "RIFF"
{
@@ -1770,7 +1707,7 @@
if (ID == 0x58565338 || ID == 0x56533631) // "8SVX" (normal) and "16SV" (FT2 sample)
{
fclose(f);
- return loadIFFSample(fileName, entryName);
+ return loadIFFSample(fileName, entryName, -1);
}
// check if it's an AIFF sample
--- a/src/pt2_sampler.c
+++ b/src/pt2_sampler.c
@@ -11,7 +11,6 @@
#include "pt2_helpers.h"
#include "pt2_textout.h"
#include "pt2_audio.h"
-#include "pt2_palette.h"
#include "pt2_tables.h"
#include "pt2_visuals.h"
#include "pt2_blep.h"
@@ -22,6 +21,8 @@
#include "pt2_config.h"
#include "pt2_bmp.h"
#include "pt2_sync.h"
+#include "pt2_rcfilter.h"
+#include "pt2_chordmaker.h"
#define CENTER_LINE_COLOR 0x303030
#define MARK_COLOR_1 0x666666 /* inverted background */
@@ -31,12 +32,6 @@
#define SAMPLE_AREA_Y_CENTER 169
#define SAMPLE_AREA_HEIGHT 64
-typedef struct sampleMixer_t
-{
- int32_t length, pos;
- uint32_t posFrac, delta;
-} sampleMixer_t;
-
sampler_t sampler; // globalized
static int32_t samOffsetScaled, lastDrawX, lastDrawY;
@@ -312,7 +307,7 @@
if (sampler.samDisplay == 0)
return 0;
- uint32_t roundingBias = (uint32_t)sampler.samDisplay >> 1;
+ const uint32_t roundingBias = (const uint32_t)sampler.samDisplay >> 1;
pos = (((uint32_t)pos * SAMPLE_AREA_WIDTH) + roundingBias) / (uint32_t)sampler.samDisplay; // rounded
pos -= samOffsetScaled;
@@ -465,7 +460,7 @@
for (y = 0; y < 64; y++)
{
for (x = 0; x < rangeLen; x++)
- dstPtr[x] = waveInvertTable[((dstPtr[x] >> 24) & 7) ^ 4]; // It's magic! ptr[x]>>24 = wave/invert color number
+ dstPtr[x] = waveInvertTable[((dstPtr[x] >> 24) & 7) ^ 4]; // ptr[x]>>24 = wave/invert color number
dstPtr += SCREEN_W;
}
@@ -523,7 +518,7 @@
void highPassSample(int32_t cutOff)
{
- int32_t smp32, i, from, to;
+ int32_t i, from, to;
double *dSampleData, dBaseFreq, dCutOff;
moduleSample_t *s;
rcFilter_t filterHi;
@@ -589,25 +584,31 @@
calcRCFilterCoeffs(dBaseFreq, dCutOff, &filterHi);
- // copy over sample data to double buffer
- for (i = 0; i < s->length; i++)
- dSampleData[i] = song->sampleData[s->offset+i];
-
clearRCFilterState(&filterHi);
if (to <= s->length)
{
+ const int8_t *smpPtr = &song->sampleData[s->offset];
for (i = from; i < to; i++)
- RCHighPassFilterMono(&filterHi, dSampleData[i], &dSampleData[i]);
+ {
+ double dSmp = smpPtr[i];
+ RCHighPassFilter(&filterHi, dSmp, &dSampleData[i]);
+ }
}
+ double dAmp = 1.0;
if (editor.normalizeFiltersFlag)
- normalize8bitDoubleSigned(dSampleData, s->length);
+ {
+ const double dPeak = getDoublePeak(dSampleData, s->length);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+ }
+ int8_t *smpPtr = &song->sampleData[s->offset];
for (i = from; i < to; i++)
{
- smp32 = (int32_t)dSampleData[i];
- CLAMP8(smp32);
- song->sampleData[s->offset + i] = (int8_t)smp32;
+ int16_t smp16 = (int16_t)round(dSampleData[i] * dAmp);
+ CLAMP8(smp16);
+ smpPtr[i] = (int8_t)smp16;
}
free(dSampleData);
@@ -619,7 +620,7 @@
void lowPassSample(int32_t cutOff)
{
- int32_t smp32, i, from, to;
+ int32_t i, from, to;
double *dSampleData, dBaseFreq, dCutOff;
moduleSample_t *s;
rcFilter_t filterLo;
@@ -692,18 +693,29 @@
clearRCFilterState(&filterLo);
if (to <= s->length)
{
+ const int8_t *smpPtr = &song->sampleData[s->offset];
for (i = from; i < to; i++)
- RCLowPassFilterMono(&filterLo, dSampleData[i], &dSampleData[i]);
+ {
+ double dSmp = smpPtr[i];
+ RCLowPassFilter(&filterLo, dSmp, &dSampleData[i]);
+ }
}
+ double dAmp = 1.0;
+
if (editor.normalizeFiltersFlag)
- normalize8bitDoubleSigned(dSampleData, s->length);
+ {
+ const double dPeak = getDoublePeak(dSampleData, s->length);
+ if (dPeak > 0.0)
+ dAmp = INT8_MAX / dPeak;
+ }
+ int8_t *smpPtr = &song->sampleData[s->offset];
for (i = from; i < to; i++)
{
- smp32 = (int32_t)dSampleData[i];
- CLAMP8(smp32);
- song->sampleData[s->offset + i] = (int8_t)smp32;
+ int16_t smp16 = (int16_t)round(dSampleData[i] * dAmp);
+ CLAMP8(smp16);
+ smpPtr[i] = (int8_t)smp16;
}
free(dSampleData);
@@ -888,20 +900,6 @@
updateWindowTitle(MOD_IS_MODIFIED);
}
-#define INTRP_QUADRATIC_TAPS 3
-#define INTRP8_QUADRATIC(s1, s2, s3, f) /* output: -32768..32767 (+ spline overshoot) */ \
-{ \
- int32_t s4, frac = (f) >> 1; \
- \
- s2 <<= 8; \
- s4 = ((s1 + s3) << (8 - 1)) - s2; \
- s4 = ((s4 * frac) >> 16) + s2; \
- s3 = (s1 + s3) << (8 - 1); \
- s1 <<= 8; \
- s3 = (s1 + s3) >> 1; \
- s1 += ((s4 - s3) * frac) >> 14; \
-} \
-
#define INTRP_LINEAR_TAPS 2
#define INTRP8_LINEAR(s1, s2, f) /* output: -127..128 */ \
s2 -= s1; \
@@ -911,237 +909,6 @@
s1 += s2; \
s1 >>= 8; \
-void mixChordSample(void)
-{
- bool smpLoopFlag;
- char smpText[22 + 1];
- int8_t *smpData, sameNotes, smpVolume;
- uint8_t smpFinetune, finetune;
- int32_t channels, samples[INTRP_QUADRATIC_TAPS], *mixData, i, j, k, pos, smpLoopStart, smpLoopLength, smpEnd;
- sampleMixer_t mixCh[4], *v;
- moduleSample_t *s;
-
- if (editor.sampleZero)
- {
- statusNotSampleZero();
- return;
- }
-
- assert(editor.currSample >= 0 && editor.currSample <= 30);
- assert(editor.tuningNote <= 35);
-
- if (editor.note1 == 36)
- {
- displayErrorMsg("NO BASENOTE!");
- return;
- }
-
- if (song->samples[editor.currSample].length == 0)
- {
- statusSampleIsEmpty();
- return;
- }
-
- // check if all notes are the same (illegal)
- sameNotes = true;
- if ((editor.note2 != 36) && (editor.note2 != editor.note1)) sameNotes = false; else editor.note2 = 36;
- if ((editor.note3 != 36) && (editor.note3 != editor.note1)) sameNotes = false; else editor.note3 = 36;
- if ((editor.note4 != 36) && (editor.note4 != editor.note1)) sameNotes = false; else editor.note4 = 36;
-
- if (sameNotes)
- {
- displayErrorMsg("ONLY ONE NOTE!");
- return;
- }
-
- // sort the notes
-
- for (i = 0; i < 3; i++)
- {
- if (editor.note2 == 36)
- {
- editor.note2 = editor.note3;
- editor.note3 = editor.note4;
- editor.note4 = 36;
- }
- }
-
- for (i = 0; i < 3; i++)
- {
- if (editor.note3 == 36)
- {
- editor.note3 = editor.note4;
- editor.note4 = 36;
- }
- }
-
- // remove eventual note duplicates
- if (editor.note4 == editor.note3) editor.note4 = 36;
- if (editor.note4 == editor.note2) editor.note4 = 36;
- if (editor.note3 == editor.note2) editor.note3 = 36;
-
- ui.updateNote1Text = true;
- ui.updateNote2Text = true;
- ui.updateNote3Text = true;
- ui.updateNote4Text = true;
-
- // setup some variables
-
- smpLoopStart = song->samples[editor.currSample].loopStart;
- smpLoopLength = song->samples[editor.currSample].loopLength;
- smpLoopFlag = (smpLoopStart + smpLoopLength) > 2;
- smpEnd = smpLoopFlag ? (smpLoopStart + smpLoopLength) : song->samples[editor.currSample].length;
- smpData = &song->sampleData[song->samples[editor.currSample].offset];
-
- if (editor.newOldFlag == 0)
- {
- // find a free sample slot for the new sample
-
- for (i = 0; i < MOD_SAMPLES; i++)
- {
- if (song->samples[i].length == 0)
- break;
- }
-
- if (i == MOD_SAMPLES)
- {
- displayErrorMsg("NO EMPTY SAMPLE!");
- return;
- }
-
- smpFinetune = song->samples[editor.currSample].fineTune;
- smpVolume = song->samples[editor.currSample].volume;
- memcpy(smpText, song->samples[editor.currSample].text, sizeof (smpText));
-
- s = &song->samples[i];
- s->fineTune = smpFinetune;
- s->volume = smpVolume;
-
- memcpy(s->text, smpText, sizeof (smpText));
- editor.currSample = (int8_t)i;
- }
- else
- {
- // overwrite current sample
- s = &song->samples[editor.currSample];
- }
-
- mixData = (int32_t *)calloc(MAX_SAMPLE_LEN, sizeof (int32_t));
- if (mixData == NULL)
- {
- statusOutOfMemory();
- return;
- }
-
- s->length = smpLoopFlag ? MAX_SAMPLE_LEN : editor.chordLength; // if sample loops, set max length
- s->loopLength = 2;
- s->loopStart = 0;
- s->text[21] = '!'; // chord sample indicator
- s->text[22] = '\0';
-
- memset(mixCh, 0, sizeof (mixCh));
-
- // setup mixing lengths and deltas
-
- finetune = s->fineTune & 0xF;
- channels = 0;
-
- if (editor.note1 < 36)
- {
- mixCh[0].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note1]);
- mixCh[0].length = (smpEnd * periodTable[(finetune * 37) + editor.note1]) / periodTable[editor.tuningNote];
- channels++;
- }
-
- if (editor.note2 < 36)
- {
- mixCh[1].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note2]);
- mixCh[1].length = (smpEnd * periodTable[(finetune * 37) + editor.note2]) / periodTable[editor.tuningNote];
- channels++;
- }
-
- if (editor.note3 < 36)
- {
- mixCh[2].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note3]);
- mixCh[2].length = (smpEnd * periodTable[(finetune * 37) + editor.note3]) / periodTable[editor.tuningNote];
- channels++;
- }
-
- if (editor.note4 < 36)
- {
- mixCh[3].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note4]);
- mixCh[3].length = (smpEnd * periodTable[(finetune * 37) + editor.note4]) / periodTable[editor.tuningNote];
- channels++;
- }
-
- // start mixing
-
- turnOffVoices();
- for (i = 0; i < channels; i++)
- {
- v = &mixCh[i];
- if (v->length <= 0)
- continue; // mix active channels only
-
- for (j = 0; j < MAX_SAMPLE_LEN; j++) // don't mix more than we can handle in a sample slot
- {
- // collect samples for interpolation
- for (k = 0; k < INTRP_QUADRATIC_TAPS; k++)
- {
- pos = v->pos + k;
- if (smpLoopFlag)
- {
- while (pos >= smpEnd)
- pos -= smpLoopLength;
-
- samples[k] = smpData[pos];
- }
- else
- {
- if (pos >= smpEnd)
- samples[k] = 0;
- else
- samples[k] = smpData[pos];
- }
- }
-
- INTRP8_QUADRATIC(samples[0], samples[1], samples[2], v->posFrac);
- mixData[j] += samples[0];
-
- v->posFrac += v->delta;
- if (v->posFrac > 0xFFFF)
- {
- v->pos += v->posFrac >> 16;
- v->posFrac &= 0xFFFF;
-
- if (smpLoopFlag)
- {
- while (v->pos >= smpEnd)
- v->pos -= smpLoopLength;
- }
- }
- }
- }
-
- normalize32bitSigned(mixData, s->length);
-
- // normalize gain and quantize to 8-bit
- for (i = 0; i < s->length; i++)
- song->sampleData[s->offset + i] = (int8_t)(mixData[i] >> 24);
-
- if (s->length < MAX_SAMPLE_LEN)
- memset(&song->sampleData[s->offset + s->length], 0, MAX_SAMPLE_LEN - s->length);
-
- // we're done
-
- free(mixData);
-
- editor.samplePos = 0;
- fixSampleBeep(s);
- updateCurrSample();
-
- updateWindowTitle(MOD_IS_MODIFIED);
-}
void samplerResample(void)
{
--- a/src/pt2_sampling.c
+++ b/src/pt2_sampling.c
@@ -23,7 +23,6 @@
#include "pt2_audio.h"
#include "pt2_tables.h"
#include "pt2_config.h"
-#include "pt2_sinc.h"
#include "pt2_sampling.h"
enum
@@ -36,13 +35,13 @@
// this may change after opening the audio input device
#define SAMPLING_BUFFER_SIZE 1024
-#define FRAC_BITS 24
-#define FRAC_SCALE (1L << 24)
-#define FRAC_MASK (FRAC_SCALE-1)
+// after several tests, these values yields a good trade-off between quality and compute time
+#define SINC_TAPS 64
+#define SINC_TAPS_BITS 6 /* log2(SINC_TAPS) */
+#define SINC_PHASES 4096
#define SAMPLE_PREVIEW_WITDH 194
#define SAMPLE_PREVIEW_HEIGHT 38
-
#define MAX_INPUT_DEVICES 99
#define VISIBLE_LIST_ENTRIES 4
@@ -50,14 +49,100 @@
static bool audioDevOpen;
static char *audioInputDevs[MAX_INPUT_DEVICES];
static uint8_t samplingNote = 33, samplingFinetune = 4; // period 124, max safe period for PAL Paula
-static int16_t displayBuffer[SAMPLING_BUFFER_SIZE], *bufferOrig, *buffer;
+static int16_t displayBuffer[SAMPLING_BUFFER_SIZE];
static int32_t samplingMode = SAMPLE_MIX, inputFrequency, roundedOutputFrequency;
static int32_t numAudioInputDevs, audioInputDevListOffset, selectedDev;
static int32_t bytesSampled, maxSamplingLength, inputBufferSize;
-static float fOutputFrequency;
-static double dOutputFrequency;
+static double dOutputFrequency, *dSincTable, *dSamplingBuffer, *dSamplingBufferOrig;
static SDL_AudioDeviceID recordDev;
+/*
+** ----------------------------------------------------------------------------------
+** Sinc code taken from the OpenMPT project (has a similar BSD license), and modified
+** ----------------------------------------------------------------------------------
+*/
+
+static double Izero(double y) // Compute Bessel function Izero(y) using a series approximation
+{
+ double s = 1.0, ds = 1.0, d = 0.0;
+
+ do
+ {
+ d = d + 2.0;
+ ds = ds * (y * y) / (d * d);
+ s = s + ds;
+ }
+ while (ds > 1E-7 * s);
+
+ return s;
+}
+
+static bool initSincTable(double cutoff)
+{
+ if (cutoff > 0.999)
+ cutoff = 0.999;
+
+ dSincTable = (double *)malloc(SINC_TAPS * SINC_PHASES * sizeof (double));
+ if (dSincTable == NULL)
+ return false;
+
+ const double beta = 9.6377; // this value can maybe be tweaked (we do downsampling only)
+ const double izeroBeta = Izero(beta);
+ const double kPi = 4.0 * atan(1.0) * cutoff;
+
+#define MID_TAP ((SINC_TAPS/2)*SINC_PHASES)
+
+ for (int32_t i = 0; i < SINC_TAPS*SINC_PHASES; i++)
+ {
+ double fsinc;
+ int32_t ix = (SINC_TAPS-1) - (i & (SINC_TAPS-1));
+
+ ix = (ix * SINC_PHASES) + (i >> SINC_TAPS_BITS);
+ if (ix == MID_TAP)
+ {
+ fsinc = 1.0;
+ }
+ else
+ {
+ const double x = (ix - MID_TAP) * (1.0 / SINC_PHASES);
+ const double xPi = x * kPi;
+
+ const double xMul = 1.0 / ((SINC_TAPS/2) * (SINC_TAPS/2));
+ fsinc = sin(xPi) * Izero(beta * sqrt(1.0 - x * x * xMul)) / (izeroBeta * xPi); // Kaiser window
+ }
+
+ dSincTable[i] = fsinc * cutoff;
+ }
+
+ return true;
+}
+
+static void freeSincTable(void)
+{
+ if (dSincTable != NULL)
+ {
+ free(dSincTable);
+ dSincTable = NULL;
+ }
+}
+
+static double sinc(const double *dSmpData, const double dPhase)
+{
+ const int32_t phase = (int32_t)(dPhase * SINC_PHASES);
+ const double *dSincLUT = &dSincTable[phase << SINC_TAPS_BITS];
+
+ double dSmp = 0.0;
+ for (int32_t i = 0; i < SINC_TAPS; i++)
+ dSmp += dSmpData[i] * dSincLUT[i];
+
+ return dSmp;
+}
+
+/*
+** ----------------------------------------------------------------------------------
+** ----------------------------------------------------------------------------------
+*/
+
static void listAudioDevices(void);
static void updateOutputFrequency(void)
@@ -70,8 +155,7 @@
period = 113;
dOutputFrequency = (double)PAULA_PAL_CLK / period;
- fOutputFrequency = (float)dOutputFrequency;
- roundedOutputFrequency = (int32_t)(fOutputFrequency + 0.5f);
+ roundedOutputFrequency = (int32_t)(dOutputFrequency + 0.5);
}
static void SDLCALL samplingCallback(void *userdata, Uint8 *stream, int len)
@@ -116,22 +200,22 @@
const int16_t *L = (int16_t *)stream;
const int16_t *R = ((int16_t *)stream) + 1;
- int16_t *dst16 = &buffer[bytesSampled];
+ double *dSmp = &dSamplingBuffer[bytesSampled];
if (samplingMode == SAMPLE_LEFT)
{
for (int32_t i = 0; i < len; i++)
- dst16[i] = L[i << 1];
+ dSmp[i] = L[i << 1] * (1.0 / 32768.0);
}
else if (samplingMode == SAMPLE_RIGHT)
{
for (int32_t i = 0; i < len; i++)
- dst16[i] = R[i << 1];
+ dSmp[i] = R[i << 1] * (1.0 / 32768.0);
}
else
{
for (int32_t i = 0; i < len; i++)
- dst16[i] = (L[i << 1] + R[i << 1]) >> 1;
+ dSmp[i] = (L[i << 1] + R[i << 1]) * (1.0 / (32768.0 * 2.0));
}
bytesSampled += len;
@@ -153,6 +237,7 @@
SDL_CloseAudioDevice(recordDev);
recordDev = 0;
}
+ callbackBusy = false;
}
static void startInputAudio(void)
@@ -187,12 +272,6 @@
SDL_PauseAudioDevice(recordDev, false);
}
-static void changeStatusText(const char *text)
-{
- fillRect(88, 127, 17*FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_GENBKG]);
- textOut(88, 127, text, video.palette[PAL_GENTXT]);
-}
-
static void selectAudioDevice(int32_t dev)
{
if (dev < 0)
@@ -480,14 +559,18 @@
maxSamplingLength = (int32_t)(ceil((65534.0*inputFrequency) / dOutputFrequency)) + 1;
- bufferOrig = (int16_t *)calloc(SINC_TAPS + maxSamplingLength + SINC_TAPS, sizeof (int16_t));
- if (bufferOrig == NULL)
+ int32_t allocLen = (SINC_TAPS/2) + maxSamplingLength + (SINC_TAPS/2);
+ dSamplingBufferOrig = (double *)malloc(allocLen * sizeof (double));
+ if (dSamplingBufferOrig == NULL)
{
statusOutOfMemory();
return;
}
- buffer = bufferOrig + SINC_TAPS; // allow negative look-up for sinc taps
+ dSamplingBuffer = dSamplingBufferOrig + (SINC_TAPS/2); // allow negative look-up for sinc taps
+ // clear tap area
+ memset(dSamplingBufferOrig, 0, (SINC_TAPS/2) * sizeof (double));
+
bytesSampled = 0;
audio.isSampling = true;
samplingEnded = false;
@@ -498,8 +581,11 @@
setStatusMessage("SAMPLING ...", NO_CARRY);
}
-static uint16_t downsampleSamplingBuffer(void)
+static int32_t downsampleSamplingBuffer(void)
{
+ // clear tap area
+ memset(&dSamplingBuffer[bytesSampled], 0, (SINC_TAPS/2) * sizeof (double));
+
const int32_t readLength = bytesSampled;
const double dRatio = dOutputFrequency / inputFrequency;
@@ -507,102 +593,67 @@
if (writeLength > MAX_SAMPLE_LEN)
writeLength = MAX_SAMPLE_LEN;
- //config.normalizeSampling = false;
-
- double *dBuffer = NULL;
- if (config.normalizeSampling)
+ double *dBuffer = (double *)malloc(writeLength * sizeof (double));
+ if (dBuffer == NULL)
{
- dBuffer = (double *)malloc(writeLength * sizeof (double));
- if (dBuffer == NULL)
- {
- statusOutOfMemory();
- return 0;
- }
+ statusOutOfMemory();
+ return -1;
}
- const double dCutoff = dRatio * 0.97; // slightly below nyquist
- if (!initSinc(dCutoff))
+ if (!initSincTable(dRatio))
{
- if (config.normalizeSampling)
- free(dBuffer);
-
+ free(dBuffer);
statusOutOfMemory();
- return 0;
+ return -1;
}
- changeStatusText("DOWNSAMPLING ...");
- flipFrame();
-
// downsample
int8_t *output = &song->sampleData[song->samples[editor.currSample].offset];
-
const double dDelta = inputFrequency / dOutputFrequency;
- int16_t *smpPtr = &buffer[-((SINC_TAPS/2)-1)]; // pre-centered (this is safe, look at how bufferOrig is alloc'd)
- double dFrac = 0.0;
- if (config.normalizeSampling)
+ // pre-centered (this is safe, look at how fSamplingBufferOrig is alloc'd)
+ const double *dSmpPtr = &dSamplingBuffer[-((SINC_TAPS/2)-1)];
+
+ double dPhase = 0.0;
+ double dPeakAmp = 0.0;
+ for (int32_t i = 0; i < writeLength; i++)
{
- double dPeakAmp = 0.0;
- for (int32_t i = 0; i < writeLength; i++) // up to 65534 bytes
- {
- double dSmp = sinc(smpPtr, dFrac);
+ double dSmp = sinc(dSmpPtr, dPhase);
+ dBuffer[i] = dSmp;
- dFrac += dDelta;
- int32_t wholeSamples = (int32_t)dFrac;
- dFrac -= wholeSamples;
- smpPtr += wholeSamples;
+ dSmp = fabs(dSmp);
+ if (dSmp > dPeakAmp)
+ dPeakAmp = dSmp;
- const double dAbsSmp = fabs(dSmp);
- if (dAbsSmp > dPeakAmp)
- dPeakAmp = dAbsSmp;
+ dPhase += dDelta;
+ const int32_t wholeSamples = (const int32_t)dPhase;
+ dPhase -= wholeSamples;
+ dSmpPtr += wholeSamples;
+ }
- dBuffer[i] = dSmp;
- }
+ freeSincTable();
- // normalize
+ // normalize
- double dAmp = INT8_MAX / dPeakAmp;
+ double dAmp = INT8_MAX / dPeakAmp;
- /* If we have to amplify THIS much, it would mean that the gain was extremely low.
- ** We don't want to amplify a ton of noise, so keep it quantized to zero (silence).
- */
- const double dAmp_dB = 20.0*log10(dAmp);
- if (dAmp_dB > 40.0)
- dAmp = 0.0;
+ /* If we have to amplify THIS much, it would mean that the gain was extremely low.
+ ** We don't want the result to be 99% noise, so keep it quantized to zero (silence).
+ */
+ const double dAmp_dB = 20.0 * log10(dAmp / 128.0);
+ if (dAmp_dB > 50.0)
+ dAmp = 0.0;
- for (int32_t i = 0; i < writeLength; i++)
- {
- /* To round the sample is probably incorrect, but it aliases audibly
- ** less after sampling a 1kHz sine wave, so I'll stick with it for now.
- ** Also just a note: Dithering is not very suitable for samples being
- ** played at lower pitches, hence why I don't dithering.
- */
- const double dSmp = dBuffer[i] * dAmp;
- int32_t smp32 = (int32_t)round(dSmp);
- output[i] = (int8_t)CLAMP(smp32, -128, 127);
- }
- }
- else
+ for (int32_t i = 0; i < writeLength; i++)
{
- for (int32_t i = 0; i < writeLength; i++) // up to 65534 bytes
- {
- const double dSmp = sinc(smpPtr, dFrac);
- int32_t smp32 = (int32_t)round(dSmp);
- output[i] = (int8_t)CLAMP(smp32, -128, 127);
-
- dFrac += dDelta;
- int32_t wholeSamples = (int32_t)dFrac;
- dFrac -= wholeSamples;
- smpPtr += wholeSamples;
- }
+ const int32_t smp32 = (const int32_t)round(dBuffer[i] * dAmp);
+ assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+ output[i] = (int8_t)smp32;
}
- freeSinc();
- if (config.normalizeSampling)
- free(dBuffer);
-
- return (uint16_t)writeLength;
+ free(dBuffer);
+ return writeLength;
}
void stopSampling(void)
@@ -611,9 +662,15 @@
audio.isSampling = false;
int32_t newLength = downsampleSamplingBuffer();
- if (newLength == 0)
+ if (newLength == -1)
return; // out of memory
+ if (dSamplingBufferOrig != NULL)
+ {
+ free(dSamplingBufferOrig);
+ dSamplingBufferOrig = NULL;
+ }
+
moduleSample_t *s = &song->samples[editor.currSample];
s->length = (uint16_t)newLength;
s->fineTune = samplingFinetune;
@@ -622,15 +679,8 @@
s->volume = 64;
fixSampleBeep(s);
- if (bufferOrig != NULL)
- {
- free(bufferOrig);
- bufferOrig = NULL;
- }
-
pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
- displayMsg("SAMPLING DONE !");
- setMsgPointer();
+ statusAllRight();
showCurrSample();
}
@@ -733,15 +783,30 @@
if (!mouse.leftButtonPressed)
return;
- if (audio.isSampling)
- stopSampling();
-
mouse.lastSamplingButton = -1;
mouse.repeatCounter = 0;
+ if (audio.isSampling)
+ {
+ stopSampling();
+ return;
+ }
+
// check buttons
const int32_t mx = mouse.x;
const int32_t my = mouse.y;
+
+ if (mx >= 182 && mx <= 243 && my >= 0 && my <= 10) // STOP (main UI)
+ {
+ turnOffVoices();
+ }
+
+ if (mx >= 6 && mx <= 25 && my >= 124 && my <= 133) // EXIT (main UI)
+ {
+ ui.samplingBoxShown = false;
+ removeSamplingBox();
+ exitFromSam();
+ }
if (mx >= 98 && mx <= 108 && my >= 44 && my <= 54) // SAMPLE UP (main UI)
{
--- a/src/pt2_scopes.c
+++ b/src/pt2_scopes.c
@@ -1,8 +1,3 @@
-// for finding memory leaks in debug mode with Visual Studio
-#if defined _DEBUG && defined _MSC_VER
-#include <crtdbg.h>
-#endif
-
#include <stdint.h>
#include <stdbool.h>
#include <math.h> // modf()
@@ -13,8 +8,6 @@
#include "pt2_helpers.h"
#include "pt2_visuals.h"
#include "pt2_scopes.h"
-#include "pt2_sampler.h"
-#include "pt2_palette.h"
#include "pt2_tables.h"
#include "pt2_structs.h"
#include "pt2_config.h"
@@ -25,7 +18,7 @@
static int32_t oldPeriod = -1;
static uint32_t scopeTimeLen, scopeTimeLenFrac;
static uint64_t timeNext64, timeNext64Frac;
-static float fOldScopeDelta;
+static double dOldScopeDelta;
static SDL_Thread *scopeThread;
scope_t scope[AMIGA_VOICES]; // global
@@ -33,6 +26,7 @@
void resetCachedScopePeriod(void)
{
oldPeriod = -1;
+ dOldScopeDelta = 0.0;
}
// this is quite hackish, but fixes sample swapping issues
@@ -105,11 +99,11 @@
if (period != oldPeriod)
{
oldPeriod = period;
- const float fPeriodToScopeDeltaDiv = PAULA_PAL_CLK / (float)SCOPE_HZ;
- fOldScopeDelta = fPeriodToScopeDeltaDiv / period;
+ const double dPeriodToScopeDeltaDiv = PAULA_PAL_CLK / (double)SCOPE_HZ;
+ dOldScopeDelta = dPeriodToScopeDeltaDiv / period;
}
- scope[ch].fDelta = fOldScopeDelta;
+ scope[ch].dDelta = dOldScopeDelta;
}
void scopeTrigger(int32_t ch)
@@ -125,7 +119,7 @@
if (newLength < 2)
newLength = 2; // for safety
- tempState.fPhase = 0.0f;
+ tempState.dPhase = 0.0;
tempState.pos = 0;
tempState.data = newData;
tempState.length = newLength;
@@ -156,10 +150,10 @@
if (!tempState.active)
continue; // scope is not active
- tempState.fPhase += tempState.fDelta;
+ tempState.dPhase += tempState.dDelta;
- const int32_t wholeSamples = (int32_t)tempState.fPhase;
- tempState.fPhase -= wholeSamples;
+ const int32_t wholeSamples = (int32_t)tempState.dPhase;
+ tempState.dPhase -= wholeSamples;
tempState.pos += wholeSamples;
if (tempState.pos >= tempState.length)
@@ -208,7 +202,8 @@
if (!tmpScope.active || tmpScope.data == NULL || tmpScope.volume == 0 || tmpScope.length == 0)
continue;
- int32_t samplesToScan = (int32_t)tmpScope.fDelta; // amount of integer samples getting skipped every frame
+ // amount of integer samples getting skipped every frame
+ const int32_t samplesToScan = (const int32_t)tmpScope.dDelta;
if (samplesToScan <= 0)
continue;
@@ -237,11 +232,11 @@
}
}
- float fAvgAmplitude = runningAmplitude / (float)samplesToScan;
+ double dAvgAmplitude = runningAmplitude / (double)samplesToScan;
- fAvgAmplitude *= 96.0f / (128.0f * 64.0f); // normalize
+ dAvgAmplitude *= 96.0 / (128.0 * 64.0); // normalize
- int32_t vuHeight = (int32_t)(fAvgAmplitude + 0.5f); // rounded
+ int32_t vuHeight = (int32_t)(dAvgAmplitude + 0.5); // rounded
if (vuHeight > 48) // max VU-meter height
vuHeight = 48;
--- a/src/pt2_scopes.h
+++ b/src/pt2_scopes.h
@@ -5,10 +5,6 @@
#include "pt2_header.h"
#include "pt2_structs.h"
-#define SCOPE_FRAC_BITS 16
-#define SCOPE_FRAC_SCALE (1UL << 16)
-#define SCOPE_FRAC_MASK (SCOPE_FRAC_SCALE-1)
-
typedef struct scope_t
{
const int8_t *data;
@@ -16,7 +12,7 @@
uint8_t volume;
int32_t length, pos;
- float fDelta, fPhase;
+ double dDelta, dPhase;
const int8_t *newData;
int32_t newLength;
} scope_t;
--- a/src/pt2_sinc.c
+++ /dev/null
@@ -1,95 +1,0 @@
-/* These routines are heavily based upon code from
-** the OpenMPT project (Tables.cpp), which has a
-** similar license.
-**
-** This code is not very readable, as I tried to
-** make it as optimized as I could. The reason I don't
-** make one big pre-calculated table is because I want
-** *many* taps while preserving fractional precision.
-**
-** There might also be some errors in how I wrote this,
-** but so far it sounds okay to my ears.
-** Let me know if I did some crucials mistakes here!
-*/
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <assert.h>
-#include <math.h>
-#include "pt2_sinc.h"
-
-#define CENTER_TAP ((SINC_TAPS / 2) - 1)
-
-static double *dWindowLUT, dKPi;
-
-// Compute Bessel function Izero(y) using a series approximation
-static double Izero(double y)
-{
- double s = 1.0, ds = 1.0, d = 0.0;
-
- do
- {
- d = d + 2.0;
- ds = ds * (y * y) / (d * d);
- s = s + ds;
- }
- while (ds > 1E-7 * s);
-
- return s;
-}
-
-bool initSinc(double dCutoff) // dCutoff = 0.0 .. 0.999
-{
- assert(SINC_TAPS > 0);
- if (dCutoff > 0.999)
- dCutoff = 0.999;
-
- const double dBeta = 9.6377;
-
- dKPi = M_PI * dCutoff;
-
- // generate window table
-
- dWindowLUT = (double *)malloc(SINC_TAPS * sizeof (double));
- if (dWindowLUT == NULL)
- return false;
-
- const double dMul1 = 1.0 / ((SINC_TAPS/2) * (SINC_TAPS/2));
- const double dMul2 = (1.0 / Izero(dBeta)) * dCutoff;
-
- double dX = CENTER_TAP;
- for (int32_t i = 0; i < SINC_TAPS; i++)
- {
- dWindowLUT[i] = Izero(dBeta * sqrt(1.0 - dX * dX * dMul1)) * dMul2; // Kaiser window
- dX -= 1.0;
- }
-
- return true;
-}
-
-void freeSinc(void)
-{
- if (dWindowLUT != NULL)
- {
- free(dWindowLUT);
- dWindowLUT = NULL;
- }
-}
-
-double sinc(int16_t *smpPtr16, double dFrac)
-{
- double dSmp = 0.0;
- double dX = (CENTER_TAP + dFrac) * dKPi;
-
- for (int32_t i = 0; i < SINC_TAPS; i++)
- {
- const double dSinc = (sin(dX) / dX) * dWindowLUT[i]; // if only I could replace this div with a mul...
- dSmp += smpPtr16[i] * dSinc;
- dX -= dKPi;
- }
-
- dSmp *= 1.0 / (SINC_TAPS / 2); // normalize (XXX: This is probably not how to do it?)
-
- return dSmp;
-}
--- a/src/pt2_sinc.h
+++ /dev/null
@@ -1,14 +1,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <stdbool.h>
-
-#if defined __amd64__ || defined __i386__ || defined _WIN64 || defined _WIN32
-#define SINC_TAPS 512
-#else
-#define SINC_TAPS 128
-#endif
-
-bool initSinc(double dCutoff); // 0.0 .. 0.999
-double sinc(int16_t *smpPtr16, double dFrac);
-void freeSinc(void);
--- a/src/pt2_structs.h
+++ b/src/pt2_structs.h
@@ -173,7 +173,7 @@
uint8_t blockFromPos, blockToPos, timingMode, f6Pos, f7Pos, f8Pos, f9Pos, f10Pos, keyOctave, pNoteFlag;
uint8_t tuningNote, resampleNote, initialTempo, initialSpeed, editMoveAdd;
- int16_t *pat2SmpBuf, modulateSpeed;
+ int16_t modulateSpeed;
uint16_t metroSpeed, metroChannel, sampleVol, samplePos, chordLength;
uint16_t effectMacros[10], oldTempo, currPlayNote, vol1, vol2, lpCutOff, hpCutOff;
uint16_t smpRedoLoopStarts[MOD_SAMPLES], smpRedoLoopLengths[MOD_SAMPLES], smpRedoLengths[MOD_SAMPLES];
@@ -180,7 +180,7 @@
int32_t modulatePos, modulateOffset, markStartOfs, markEndOfs, pat2SmpPos;
uint32_t vblankTimeLen, vblankTimeLenFrac;
uint64_t musicTime64;
- double dPerfFreq, dPerfFreqMulMicro;
+ double dPerfFreq, dPerfFreqMulMicro, *dPat2SmpBuf;
note_t trackBuffer[MOD_ROWS], cmdsBuffer[MOD_ROWS], blockBuffer[MOD_ROWS];
note_t patternBuffer[MOD_ROWS * AMIGA_VOICES], undoBuffer[MOD_ROWS * AMIGA_VOICES];
SDL_Thread *mod2WavThread, *pat2SmpThread;
--- a/src/pt2_textout.c
+++ b/src/pt2_textout.c
@@ -4,7 +4,6 @@
#include "pt2_header.h"
#include "pt2_helpers.h"
#include "pt2_tables.h"
-#include "pt2_palette.h"
#include "pt2_visuals.h"
#include "pt2_structs.h"
#include "pt2_bmp.h"
--- a/src/pt2_visuals.c
+++ b/src/pt2_visuals.c
@@ -23,7 +23,6 @@
#include "pt2_keyboard.h"
#include "pt2_mouse.h"
#include "pt2_audio.h"
-#include "pt2_palette.h"
#include "pt2_helpers.h"
#include "pt2_textout.h"
#include "pt2_tables.h"
@@ -42,6 +41,7 @@
#include "pt2_config.h"
#include "pt2_bmp.h"
#include "pt2_sampling.h"
+#include "pt2_chordmaker.h"
typedef struct sprite_t
{
@@ -185,6 +185,12 @@
displayErrorMsg("SAMPLE IS EMPTY");
}
+void changeStatusText(const char *text)
+{
+ fillRect(88, 127, 17*FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_GENBKG]);
+ textOut(88, 127, text, video.palette[PAL_GENTXT]);
+}
+
void statusNotSampleZero(void)
{
/* This rather confusing error message actually means that
@@ -1620,13 +1626,7 @@
break;
default:
- {
restoreStatusAndMousePointer();
- editor.errorMsgActive = true;
- editor.errorMsgBlock = true;
- editor.errorMsgCounter = 0;
- setErrPointer();
- }
break;
}
--- a/src/pt2_visuals.h
+++ b/src/pt2_visuals.h
@@ -33,6 +33,7 @@
void statusOutOfMemory(void);
void statusSampleIsEmpty(void);
void statusNotSampleZero(void);
+void changeStatusText(const char *text);
void setupPerfFreq(void);
void setupWaitVBL(void);
--- a/vs2019_project/pt2-clone/protracker.ini
+++ b/vs2019_project/pt2-clone/protracker.ini
@@ -220,39 +220,16 @@
;
SAMPLINGFREQ=44100
-; Normalize sampled audio before converting to 8-bit
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: This one is for the audio sampling feature in the SAMPLER
-; screen. If it's set to TRUE, it will normalize the gain before it
-; converts the sample to 8-bit in the end. This will preserve as much
-; amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+; Syntax: A500 or A1200
+; Default value: A1200
+; Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+; A1200 has sharper sound but more aliasing, while A500 has more
+; filtered (muddy) sound but less aliasing. The filter model can
+; also be toggled by pressing F12 in the program.
;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
-; Audio buffer size
-; Syntax: Number, in samples
-; Default value: 1024
-; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-; isn't necessarily the actual value the audio API decides to use.
-; Lower means less audio latency but possible audio issues, higher
-; means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-; Syntax: TRUE or FALSE
-; Default value: FALSE
-; Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-; filter present in almost all Amigas. This will make the sound a bit
-; muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-; can also be toggled in the tracker by pressing F12. This must not be
-; confused with the "LED" filter which can be toggled with the pattern
-; command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
; Stereo separation
; Syntax: 0 to 100 (percent)
; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
;
STEREOSEPARATION=20
-
-; Low-pass samples before getting 2x downsampled during loading (if requested)
-; Syntax: TRUE or FALSE
-; Default value: TRUE
-; Comment: Set to false if you want slightly sharper sound when loading
-; samples that are 2x downsampled (if requested).
-; Keep in mind that you might get more aliasing in the sound if you
-; disable this, and certain sounds with a lot of treble might sound
-; weird (f.ex. hi-hats and cymbals). If the sample you load have a
-; frequency below 22kHz, it will never be downsampled (and thus this
-; setting changes nothing). I recommend keeping this set to TRUE.
+; Audio buffer size
+; Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+; Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+; (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+; isn't necessarily the actual value the audio API decides to use.
+; Lower means less audio latency but possible audio issues, higher
+; means more audio latency but less chance for issues.
;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
; End of config file
\ No newline at end of file
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj
@@ -88,7 +88,7 @@
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
- <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+ <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<IntrinsicFunctions>true</IntrinsicFunctions>
@@ -99,12 +99,12 @@
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<FloatingPointModel>Fast</FloatingPointModel>
<DebugInformationFormat>None</DebugInformationFormat>
- <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
<OmitFramePointers>true</OmitFramePointers>
<CompileAsWinRT>false</CompileAsWinRT>
<BufferSecurityCheck>false</BufferSecurityCheck>
<TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -135,7 +135,7 @@
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
- <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+ <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<IntrinsicFunctions>true</IntrinsicFunctions>
<StringPooling>true</StringPooling>
@@ -145,11 +145,11 @@
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<FloatingPointModel>Fast</FloatingPointModel>
<DebugInformationFormat>None</DebugInformationFormat>
- <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
<OmitFramePointers>true</OmitFramePointers>
<BufferSecurityCheck>false</BufferSecurityCheck>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -177,7 +177,7 @@
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+ <PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<FloatingPointModel>Fast</FloatingPointModel>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
@@ -205,7 +205,7 @@
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+ <PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<FloatingPointModel>Fast</FloatingPointModel>
<OmitFramePointers>false</OmitFramePointers>
<TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
@@ -242,6 +242,7 @@
<ClInclude Include="..\..\src\pt2_audio.h" />
<ClInclude Include="..\..\src\pt2_blep.h" />
<ClInclude Include="..\..\src\pt2_bmp.h" />
+ <ClInclude Include="..\..\src\pt2_chordmaker.h" />
<ClInclude Include="..\..\src\pt2_config.h" />
<ClInclude Include="..\..\src\pt2_diskop.h" />
<ClInclude Include="..\..\src\pt2_edit.h" />
@@ -248,6 +249,7 @@
<ClInclude Include="..\..\src\pt2_header.h" />
<ClInclude Include="..\..\src\pt2_helpers.h" />
<ClInclude Include="..\..\src\pt2_keyboard.h" />
+ <ClInclude Include="..\..\src\pt2_ledfilter.h" />
<ClInclude Include="..\..\src\pt2_mod2wav.h" />
<ClInclude Include="..\..\src\pt2_module_loader.h" />
<ClInclude Include="..\..\src\pt2_module_saver.h" />
@@ -255,12 +257,13 @@
<ClInclude Include="..\..\src\pt2_palette.h" />
<ClInclude Include="..\..\src\pt2_pat2smp.h" />
<ClInclude Include="..\..\src\pt2_pattern_viewer.h" />
+ <ClInclude Include="..\..\src\pt2_rcfilter.h" />
+ <ClInclude Include="..\..\src\pt2_downsamplers2x.h" />
<ClInclude Include="..\..\src\pt2_sample_loader.h" />
<ClInclude Include="..\..\src\pt2_sampler.h" />
<ClInclude Include="..\..\src\pt2_sample_saver.h" />
<ClInclude Include="..\..\src\pt2_sampling.h" />
<ClInclude Include="..\..\src\pt2_scopes.h" />
- <ClInclude Include="..\..\src\pt2_sinc.h" />
<ClInclude Include="..\..\src\pt2_structs.h" />
<ClInclude Include="..\..\src\pt2_sync.h" />
<ClInclude Include="..\..\src\pt2_tables.h" />
@@ -292,14 +295,17 @@
</ClCompile>
<ClCompile Include="..\..\src\pt2_blep.c" />
<ClCompile Include="..\..\src\pt2_bmp.c" />
+ <ClCompile Include="..\..\src\pt2_chordmaker.c" />
<ClCompile Include="..\..\src\pt2_config.c" />
<ClCompile Include="..\..\src\pt2_diskop.c" />
<ClCompile Include="..\..\src\pt2_edit.c" />
<ClCompile Include="..\..\src\pt2_helpers.c" />
<ClCompile Include="..\..\src\pt2_keyboard.c" />
+ <ClCompile Include="..\..\src\pt2_ledfilter.c" />
<ClCompile Include="..\..\src\pt2_main.c" />
<ClCompile Include="..\..\src\pt2_mod2wav.c" />
<ClCompile Include="..\..\src\pt2_module_loader.c" />
+ <ClCompile Include="..\..\src\pt2_rcfilter.c" />
<ClCompile Include="..\..\src\pt2_replayer.c" />
<ClCompile Include="..\..\src\pt2_module_saver.c" />
<ClCompile Include="..\..\src\pt2_mouse.c" />
@@ -306,12 +312,12 @@
<ClCompile Include="..\..\src\pt2_palette.c" />
<ClCompile Include="..\..\src\pt2_pat2smp.c" />
<ClCompile Include="..\..\src\pt2_pattern_viewer.c" />
+ <ClCompile Include="..\..\src\pt2_downsample2x.c" />
<ClCompile Include="..\..\src\pt2_sample_loader.c" />
<ClCompile Include="..\..\src\pt2_sampler.c" />
<ClCompile Include="..\..\src\pt2_sample_saver.c" />
<ClCompile Include="..\..\src\pt2_sampling.c" />
<ClCompile Include="..\..\src\pt2_scopes.c" />
- <ClCompile Include="..\..\src\pt2_sinc.c" />
<ClCompile Include="..\..\src\pt2_structs.c" />
<ClCompile Include="..\..\src\pt2_sync.c" />
<ClCompile Include="..\..\src\pt2_tables.c" />
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
@@ -90,9 +90,18 @@
<ClInclude Include="..\..\src\pt2_sync.h">
<Filter>headers</Filter>
</ClInclude>
- <ClInclude Include="..\..\src\pt2_sinc.h">
+ <ClInclude Include="..\..\src\pt2_rcfilter.h">
<Filter>headers</Filter>
</ClInclude>
+ <ClInclude Include="..\..\src\pt2_ledfilter.h">
+ <Filter>headers</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\pt2_chordmaker.h">
+ <Filter>headers</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\pt2_downsamplers2x.h">
+ <Filter>headers</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\src\pt2_audio.c" />
@@ -177,7 +186,10 @@
<ClCompile Include="..\..\src\pt2_replayer.c" />
<ClCompile Include="..\..\src\pt2_sampling.c" />
<ClCompile Include="..\..\src\pt2_sync.c" />
- <ClCompile Include="..\..\src\pt2_sinc.c" />
+ <ClCompile Include="..\..\src\pt2_rcfilter.c" />
+ <ClCompile Include="..\..\src\pt2_ledfilter.c" />
+ <ClCompile Include="..\..\src\pt2_chordmaker.c" />
+ <ClCompile Include="..\..\src\pt2_downsample2x.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\..\src\pt2-clone.rc" />