DOSBox-X branch

Here you can discuss the development of patches.

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-27 @ 18:10

Heh, well... the SDL 1.2.15 provided by Brew on Mac OS X isn't aware of Mojave's redraw issue. But SVN does compile on OS X 64-bit.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-27 @ 18:39

I have a "mirror" of SVN here: https://github.com/joncampbell123/dosbox-svn

I trans-planted the hacked SDL1 library from DOSBox-X to give DOSBox SVN something to compile against that displays on Mojave correctly. So far, it works.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-27 @ 18:51

Using hw\vesa\MODESET in DOSLIB, I can confirm highcolor/truecolor modes show up correctly in Mac OS X if you use the OpenGL output with the latest SVN commit.

I can also confirm video update performance is poor with output=surface in OS X and very poor if the window is above 800x600 on a Retina display.

This is why DOSBox-X makes OpenGL output the default for Mac OS X builds.

EDIT: output=surface shows incorrect colors, confirmed.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-27 @ 18:55

Everything else looks correct for output=surface, it's only the highcolor modes.

The way I fixed that in DOSBox-X (going off the top of my head) was to modify the LIN15/LIN16/LIN24/LIN32 VGA scanline rendering code so that in non-OpenGL output it would reverse the RGBA order to match the BGRA (32-bit DWORD with blue in upper 8 bits, alpha in lower 8 bits) order used by Mac OS X.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-27 @ 18:58

It's fine for non-special scalers like the normal2x and so on, but not ideal if you want to use any of the other scalers since the render scalers still assume ARGB.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby hail-to-the-ryzen » 2018-12-27 @ 22:02

Is there a message logged where DOSBox-X for Mac OS X is set at:
non-OpenGL output
VGA LIN15 or higher
scaler NOT normalNx

The message could warn about running the non-normalNx scalers given they are not compatible with those conditions. Thanks for the continued work on dosbox-x and for the sdl1 fixes.
hail-to-the-ryzen
Member
 
Posts: 256
Joined: 2017-3-09 @ 01:34

Re: DOSBox-X branch

Postby hail-to-the-ryzen » 2018-12-27 @ 23:43

This is the code for the normal4x scaler:
Code: Select all
diff -rupN dosbox-Original//src/dosbox.cpp dosbox/src/dosbox.cpp
--- dosbox-Original//src/dosbox.cpp
+++ dosbox/src/dosbox.cpp
@@ -615,7 +615,7 @@ void DOSBOX_Init(void) {
    Pstring = Pmulti->GetSection()->Add_string("type",Property::Changeable::Always,"normal2x");
 
    const char *scalers[] = {
-      "none", "normal2x", "normal3x",
+      "none", "normal2x", "normal3x", "normal4x",
 #if RENDER_USE_ADVANCED_SCALERS>2
       "advmame2x", "advmame3x", "advinterp2x", "advinterp3x", "hq2x", "hq3x", "2xsai", "super2xsai", "supereagle",
 #endif
diff -rupN dosbox-Original//src/gui/render.cpp dosbox/src/gui/render.cpp
--- dosbox-Original//src/gui/render.cpp
+++ dosbox/src/gui/render.cpp
@@ -386,6 +386,8 @@ static void RENDER_Reset( void ) {
          simpleBlock = &ScaleNormal2x;
       else if (render.scale.size == 3)
          simpleBlock = &ScaleNormal3x;
+      else if (render.scale.size == 4)
+         simpleBlock = &ScaleNormal4x;
       else
          simpleBlock = &ScaleNormal1x;
       /* Maybe override them */
@@ -700,6 +702,7 @@ void RENDER_Init(Section * sec) {
    if (scaler == "none") { render.scale.op = scalerOpNormal;render.scale.size = 1; }
    else if (scaler == "normal2x") { render.scale.op = scalerOpNormal;render.scale.size = 2; }
    else if (scaler == "normal3x") { render.scale.op = scalerOpNormal;render.scale.size = 3; }
+   else if (scaler == "normal4x") { render.scale.op = scalerOpNormal;render.scale.size = 4; }
 #if RENDER_USE_ADVANCED_SCALERS>2
    else if (scaler == "advmame2x") { render.scale.op = scalerOpAdvMame;render.scale.size = 2; }
    else if (scaler == "advmame3x") { render.scale.op = scalerOpAdvMame;render.scale.size = 3; }
diff -rupN dosbox-Original//src/gui/render_scalers.cpp dosbox/src/gui/render_scalers.cpp
--- dosbox-Original//src/gui/render_scalers.cpp
+++ dosbox/src/gui/render_scalers.cpp
@@ -256,6 +256,23 @@ ScalerSimpleBlock_t ScaleNormal3x = {
 {   Normal3x_8_8_R,      Normal3x_9_15_R ,   Normal3x_9_16_R ,   Normal3x_9_32_R }
 }};
 
+ScalerSimpleBlock_t ScaleNormal4x = {
+    "Normal4x",
+    GFX_CAN_8|GFX_CAN_15|GFX_CAN_16|GFX_CAN_32,
+    4,4,{
+{    Normal4x_8_8_L,        Normal4x_8_15_L ,    Normal4x_8_16_L ,    Normal4x_8_32_L },
+{                 0,        Normal4x_15_15_L,    Normal4x_15_16_L,    Normal4x_15_32_L},
+{                 0,        Normal4x_16_15_L,    Normal4x_16_16_L,    Normal4x_16_32_L},
+{                 0,        Normal4x_32_15_L,    Normal4x_32_16_L,    Normal4x_32_32_L},
+{    Normal4x_8_8_L,        Normal4x_9_15_L ,    Normal4x_9_16_L ,    Normal4x_9_32_L }
+},{
+{    Normal4x_8_8_R,        Normal4x_8_15_R ,    Normal4x_8_16_R ,    Normal4x_8_32_R },
+{                 0,        Normal4x_15_15_R,    Normal4x_15_16_R,    Normal4x_15_32_R},
+{                 0,        Normal4x_16_15_R,    Normal4x_16_16_R,    Normal4x_16_32_R},
+{                 0,        Normal4x_32_15_R,    Normal4x_32_16_R,    Normal4x_32_32_R},
+{    Normal4x_8_8_R,        Normal4x_9_15_R ,    Normal4x_9_16_R ,    Normal4x_9_32_R }
+}};
+
 #if RENDER_USE_ADVANCED_SCALERS>0
 ScalerSimpleBlock_t ScaleTV2x = {
    "TV2x",
diff -rupN dosbox-Original//src/gui/render_scalers.h dosbox/src/gui/render_scalers.h
--- dosbox-Original//src/gui/render_scalers.h
+++ dosbox/src/gui/render_scalers.h
@@ -113,6 +113,7 @@ extern ScalerSimpleBlock_t ScaleNormalDw
 extern ScalerSimpleBlock_t ScaleNormalDh;
 extern ScalerSimpleBlock_t ScaleNormal2x;
 extern ScalerSimpleBlock_t ScaleNormal3x;
+extern ScalerSimpleBlock_t ScaleNormal4x;
 #if RENDER_USE_ADVANCED_SCALERS>0
 extern ScalerSimpleBlock_t ScaleTV2x;
 extern ScalerSimpleBlock_t ScaleTV3x;
diff -rupN dosbox-Original//src/gui/render_templates.h dosbox/src/gui/render_templates.h
--- dosbox-Original//src/gui/render_templates.h
+++ dosbox/src/gui/render_templates.h
@@ -271,6 +271,32 @@ static inline void conc3d(Cache,SBPP,DBP
 #undef SCALERHEIGHT
 #undef SCALERFUNC
 
+#define SCALERNAME      Normal4x
+#define SCALERWIDTH      4
+#define SCALERHEIGHT   4
+#define SCALERFUNC                           \
+   line0[0] = P;                        \
+   line0[1] = P;                        \
+   line0[2] = P;                        \
+   line0[3] = P;                        \
+   line1[0] = P;                        \
+   line1[1] = P;                        \
+   line1[2] = P;                        \
+   line1[3] = P;                        \
+   line2[0] = P;                        \
+   line2[1] = P;                        \
+   line2[2] = P;                        \
+   line2[3] = P;                        \
+   line3[0] = P;                        \
+   line3[1] = P;                        \
+   line3[2] = P;                        \
+   line3[3] = P;
+#include "render_simple.h"
+#undef SCALERNAME
+#undef SCALERWIDTH
+#undef SCALERHEIGHT
+#undef SCALERFUNC
+
 #define SCALERNAME      NormalDw
 #define SCALERWIDTH      2
 #define SCALERHEIGHT   1
diff -rupN dosbox-Original//src/gui/render_simple.h dosbox/src/gui/render_simple.h
--- dosbox-Original//src/gui/render_simple.h
+++ dosbox/src/gui/render_simple.h
@@ -56,6 +56,9 @@ static inline void conc4d_sub_func(const
 #if (SCALERHEIGHT > 2)
          PTYPE *line2 = WC[1];
 #endif
+#if (SCALERHEIGHT > 3)
+         PTYPE *line3 = WC[2];
+#endif
 #else
 #if (SCALERHEIGHT > 1)
       PTYPE *line1 = (PTYPE *)(((Bit8u*)line0)+ render.scale.outPitch);
@@ -63,6 +66,9 @@ static inline void conc4d_sub_func(const
 #if (SCALERHEIGHT > 2)
       PTYPE *line2 = (PTYPE *)(((Bit8u*)line0)+ render.scale.outPitch * 2);
 #endif
+#if (SCALERHEIGHT > 3)
+      PTYPE *line3 = (PTYPE *)(((Bit8u*)line0)+ render.scale.outPitch * 3);
+#endif
 #endif //defined(SCALERLINEAR)
          hadChange = 1;
             unsigned int i = block_proc; /* WARNING: assume block_proc != 0 */
@@ -78,6 +84,9 @@ static inline void conc4d_sub_func(const
 #if (SCALERHEIGHT > 2)
             line2 += SCALERWIDTH;
 #endif
+#if (SCALERHEIGHT > 3)
+            line3 += SCALERWIDTH;
+#endif
          } while (--i != 0u);
 #if defined(SCALERLINEAR)
 #if (SCALERHEIGHT > 1)

I've been testing normal5x and higher for use on very high resolution monitors.
hail-to-the-ryzen
Member
 
Posts: 256
Joined: 2017-3-09 @ 01:34

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-28 @ 01:14

Here's a patch for DOSBox SVN to fix highcolor/truecolor modes on OS X.
You do not have the required permissions to view the files attached to this post.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-28 @ 19:11

Ideas on backporting some fixes from DOSBox-X to DOSBox SVN:

https://github.com/joncampbell123/dosbox-x/issues/975

EDIT: Feel free to comment whether or not the main DOSBox project (DOSBox SVN) is interested in any of the backported features.
Last edited by TheGreatCodeholio on 2018-12-29 @ 00:17, edited 1 time in total.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-29 @ 00:00

DOSBox-X machine=mda (plain MDA) setting, backport to DOSBox SVN.

It's essentially Hercules emulation with some I/O ports and bits disabled so that the emulation acts like a plain MDA adapter.
You do not have the required permissions to view the files attached to this post.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby DosFreak » 2018-12-29 @ 01:40

Some things I'm interested in:
/EDIT in-tree SDL1
Changes for HX DOS (HX DOS is still under active development)

I haven't written a patch for it yet but I made changes to allow DOSBox to run on 95 and NT4 without Active Desktop (see attached) and I noticed you made similar changes for HX DOS support. I added a switch to configure for it. I did not factor in the LFN dosbox.conf for DOS which didn't seem to be much of an issue since DOSBox reads it well enough guess we'd have to decide if an LFN program should be used with HX DOS since people are probably trying to run LFN windows programs anyway.

Files Changed:
configure.ac
src\misc\cross.cpp
src\platform\visualc\config.h

I've been able to prooduce a working executable compiled with Mingw-W64 that works on 3.50-2000 (with DOSBox SVN) but you have to use the latest msvcrt.dll for Windows 2000 provided in a KB, you have to use Mingw-w64 win32 (not posix!) dwarf w/gcc (no clang!) , you can't use certain gcc switches and it will only work on Pentium Pro and above processors (Haven't tried recompiling Mingw-w64 for <Pentium Pro yet)

Also:
*Do not use -Ofast. This breaks compatibility with NT3.51 and 95.
****If you want to maintain 2000 and below compatibility then use -static-libstdc++ otherwise DOSBox will fail to load on 2000 and below due to libstdc++ requiring GetModuleHandleExW in Kernel32.dll

PREREQUISITES FOR RUNNING DOSBOX

MSVCRT.DLL
*Download Required for Windows NT 3.51, 95 and 98.
Copy MSVCRT.DLL to SYSTEM32 (NT) or SYSTEM (9x) directories.

Latest V6 MSVCRT:
Q932590.zip
http://www.mdgx.com/files/Q932590.EXE

Copy MSVCRT.DLL to SYSTEM32 (NT) or SYSTEM (9x) directories.

viewtopic.php?f=31&t=55706&p=636941&hilit=compilation#p609435S
https://drive.google.com/drive/folders/ ... 3aMO8U9mvV
You do not have the required permissions to view the files attached to this post.
User avatar
DosFreak
l33t++
 
Posts: 10054
Joined: 2002-6-30 @ 16:35
Location: Your Head

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-29 @ 01:46

The way I target HX DOS at the moment is by using MinGW (but NOT MinGW64!) to compile for Win32. The CFLAGS include a define to set HX_DOS which then tells DOSBox-X and the in-tree SDL1 library not to use functions and features that HX DOS does not provide (such as the menu bar and menu functions). The MinGW64 compiler produces a Win32 binary that requires Windows 2000/XP to run and won't run under HX DOS, while the original MinGW can still target Windows 95.

You *can* in fact target the original release of Windows 95 with MinGW, you just need to install MSVCRT.DLL for it to run.

The other reason for HX DOS is to compile out the asynchronous SDL1 window code I added. HX DOS does not support a child window inside a parent window across two threads. The asynchronous SDL1 window code is what allows DOSBox-X emulation to continue running even if you move/resize the window or use the menus. The only known problem is a strange deadlock that only happens in recent versions of Windows 10 when resizing.

When you say integration of SDL1, do you mean having an in-tree version of SDL1 the same way DOSBox-X does?
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby DosFreak » 2018-12-29 @ 01:58

When you say integration of SDL1, do you mean having an in-tree version of SDL1 the same way DOSBox-X does?


Yup, haven't asked Qbix what he thinks about it.

Also any changes made to SDL1 code.
User avatar
DosFreak
l33t++
 
Posts: 10054
Joined: 2002-6-30 @ 16:35
Location: Your Head

Re: DOSBox-X branch

Postby emendelson » 2018-12-29 @ 02:15

TheGreatCodeholio wrote:I have a "mirror" of SVN here: https://github.com/joncampbell123/dosbox-svn

I trans-planted the hacked SDL1 library from DOSBox-X to give DOSBox SVN something to compile against that displays on Mojave correctly. So far, it works.


As I wrote in your Issues page on Github, this is spectacularly useful because it makes it possible for anyone to build 64-bit SVN on a Mac - something that only Dominus has succeeded in doing so far!

It's not your job to support DOSBox, but it would be good if this could be built on High Sierra and earlier versions, not just on Mojave. If i build on High Sierra, the executable segfaults on launch (and the same executable segfaults if I run it under Mojave).

If I build in Mojave, the executable runs in Mojave, but it segfaults if I copy it to a High Sierra system and run it there.

If anyone knows of an easy fix, it would be very good to have it.
emendelson
Oldbie
 
Posts: 757
Joined: 2010-2-14 @ 02:00

Re: DOSBox-X branch

Postby hail-to-the-ryzen » 2018-12-29 @ 02:35

Here is reference code for the dosbox-x mixer (untested; for sb and opl):
Code: Select all
[code]diff -rupN dosbox-svn/include/mixer.h dosbox-mixer//include/mixer.h
--- dosbox-svn/include/mixer.h
+++ dosbox-mixer//include/mixer.h
@@ -49,13 +49,22 @@ public:
    void SetVolume(float _left,float _right);
    void SetScale( float f );
    void UpdateVolume(void);
-   void SetFreq(Bitu _freq);
-   void Mix(Bitu _needed);
+   void SetFreq(Bitu _freq,Bitu _den=1U);
+   void Mix(Bitu whole,Bitu frac);
    void AddSilence(void);         //Fill up until needed
+   void EndFrame(Bitu samples);
+
+   template<class Type,bool stereo,bool signeddata,bool nativeorder>
+   void loadCurrentSample(Bitu &len, const Type* &data);
 
    template<class Type,bool stereo,bool signeddata,bool nativeorder>
    void AddSamples(Bitu len, const Type* data);
+   double timeSinceLastSample(void);
+
+   bool runSampleInterpolation(const Bitu upto);
 
+   void padFillSampleInterpolation(const Bitu upto);
+   void finishSampleInterpolation(const Bitu upto);
    void AddSamples_m8(Bitu len, const Bit8u * data);
    void AddSamples_s8(Bitu len, const Bit8u * data);
    void AddSamples_m8s(Bitu len, const Bit8s * data);
@@ -72,8 +81,6 @@ public:
    void AddSamples_s16u_nonnative(Bitu len, const Bit16u * data);
    void AddSamples_m32_nonnative(Bitu len, const Bit32s * data);
    void AddSamples_s32_nonnative(Bitu len, const Bit32s * data);
-   
-   void AddStretched(Bitu len,Bit16s * data);      //Strech block up into needed data
 
    void FillUp(void);
    void Enable(bool _yesno);
@@ -81,18 +88,17 @@ public:
    float volmain[2];
    float scale;
    Bit32s volmul[2];
-   
-   //This gets added the frequency counter each mixer step
-   Bitu freq_add;
-   //When this flows over a new sample needs to be read from the device
-   Bitu freq_counter;
-   //Timing on how many samples have been done and were needed by th emixer
-   Bitu done, needed;
-   //Previous and next samples
-   Bits prevSample[2];
-   Bits nextSample[2];
+   unsigned int freq_f;
+   unsigned int rendering_to_n,rendering_to_d;
+   unsigned int rend_n,rend_d;
+   unsigned int freq_n,freq_d,freq_d_orig;
+   bool current_loaded;
+   Bit32s current[2],last[2],delta[2],max_change;
+   Bit32s msbuffer[2048][2];      // more than enough for 1ms of audio, at mixer sample rate
+   Bits last_sample_write;
+   Bitu msbuffer_o;
+   Bitu msbuffer_i;
    const char * name;
-   bool interpolate;
    bool enabled;
    MixerChannel * next;
 };
diff -rupN dosbox-svn/src/hardware/adlib.cpp dosbox-mixer//src/hardware/adlib.cpp
--- dosbox-svn/src/hardware/adlib.cpp
+++ dosbox-mixer//src/hardware/adlib.cpp
@@ -703,6 +703,11 @@ static Bitu OPL_Read(Bitu port,Bitu iole
 }
 
 void OPL_Write(Bitu port,Bitu val,Bitu iolen) {
+   // if writing the data port, assume a change in OPL state that should be reflected immediately.
+   // this is a way to render "sample accurate" without needing "sample accurate" mode in the mixer.
+   // CHGOLF's Adlib digital audio hack works fine with this hack.
+   if (port&1) module->mixerChan->FillUp();
+
    module->PortWrite( port, val, iolen );
 }
 
diff -rupN dosbox-svn/src/hardware/mixer.cpp dosbox-mixer//src/hardware/mixer.cpp
--- dosbox-svn/src/hardware/mixer.cpp
+++ dosbox-mixer//src/hardware/mixer.cpp
@@ -50,42 +50,39 @@
 #include "midi.h"
 
 #define MIXER_SSIZE 4
-
-//#define MIXER_SHIFT 14
-//#define MIXER_REMAIN ((1<<MIXER_SHIFT)-1)
-
 #define MIXER_VOLSHIFT 13
 
-#define FREQ_SHIFT 14
-#define FREQ_NEXT ( 1 << FREQ_SHIFT)
-#define FREQ_MASK ( FREQ_NEXT -1 )
-
-#define TICK_SHIFT 14
-#define TICK_NEXT ( 1 << TICK_SHIFT)
-#define TICK_MASK (TICK_NEXT -1)
-
-
 static INLINE Bit16s MIXER_CLIP(Bits SAMP) {
    if (SAMP < MAX_AUDIO) {
       if (SAMP > MIN_AUDIO)
          return SAMP;
-      else return MIN_AUDIO;
-   } else return MAX_AUDIO;
+      else
+         return MIN_AUDIO;
+   } else {
+      return MAX_AUDIO;
+   }
 }
 
+struct mixedFraction {
+   unsigned int      w;
+   unsigned int      fn,fd;
+};
+
 static struct {
-   Bit32s work[MIXER_BUFSIZE][2];
-   //Write/Read pointers for the buffer
-   Bitu pos,done;
-   Bitu needed, min_needed, max_needed;
-   //For every millisecond tick how many samples need to be generated
-   Bit32u tick_add;
-   Bit32u tick_counter;
-   float mastervol[2];
-   MixerChannel * channels;
-   bool nosound;
-   Bit32u freq;
-   Bit32u blocksize;
+   Bit32s         work[MIXER_BUFSIZE][2];
+   Bitu         work_in,work_out,work_wrap;
+   Bitu         pos,done;
+   float         mastervol[2];
+   float              recordvol[2];
+   MixerChannel*      channels;
+   Bit32u         freq;
+   Bit32u         blocksize;
+   struct mixedFraction   samples_per_ms;
+   struct mixedFraction   samples_this_ms;
+   struct mixedFraction   samples_rendered_ms;
+   bool         nosound;
+       bool               prebuffer_wait;
+       Bitu               prebuffer_samples;
 } mixer;
 
 Bit8u MixTemp[MIXER_BUFSIZE];
@@ -93,13 +90,24 @@ Bit8u MixTemp[MIXER_BUFSIZE];
 MixerChannel * MIXER_AddChannel(MIXER_Handler handler,Bitu freq,const char * name) {
    MixerChannel * chan=new MixerChannel();
    chan->scale = 1.0;
+   chan->last_sample_write = 0;
+   chan->current_loaded = false;
    chan->handler=handler;
    chan->name=name;
+   chan->msbuffer_i = 0;
+   chan->msbuffer_o = 0;
+   chan->freq_n = chan->freq_d = 1;
+
+   chan->freq_d_orig = 1;
+   chan->freq_f = 0;
    chan->SetFreq(freq);
    chan->next=mixer.channels;
    chan->SetVolume(1,1);
    chan->enabled=false;
-   chan->interpolate = false;
+   chan->last[0] = chan->last[1] = 0;
+   chan->delta[0] = chan->delta[1] = 0;
+   chan->current[0] = chan->current[1] = 0;
+
    mixer.channels=chan;
    return chan;
 }
@@ -128,8 +136,8 @@ void MIXER_DelChannel(MixerChannel* delc
 }
 
 void MixerChannel::UpdateVolume(void) {
-   volmul[0]=(Bits)((1 << MIXER_VOLSHIFT)*scale*volmain[0]*mixer.mastervol[0]);
-   volmul[1]=(Bits)((1 << MIXER_VOLSHIFT)*scale*volmain[1]*mixer.mastervol[1]);
+   volmul[0]=(Bits)((1 << MIXER_VOLSHIFT)*scale*volmain[0]);
+   volmul[1]=(Bits)((1 << MIXER_VOLSHIFT)*scale*volmain[1]);
 }
 
 void MixerChannel::SetVolume(float _left,float _right) {
@@ -143,192 +151,231 @@ void MixerChannel::SetScale( float f ) {
    UpdateVolume();
 }
 
+static void MIXER_FillUp(void);
+
 void MixerChannel::Enable(bool _yesno) {
    if (_yesno==enabled) return;
    enabled=_yesno;
-   if (enabled) {
-      freq_counter = 0;
-      SDL_LockAudio();
-      if (done<mixer.done) done=mixer.done;
-      SDL_UnlockAudio();
-   }
+   if (!enabled) freq_f=0;
 }
 
-void MixerChannel::SetFreq(Bitu freq) {
-   freq_add=(freq<<FREQ_SHIFT)/mixer.freq;
+void MixerChannel::SetFreq(Bitu _freq,Bitu _den) {
+   if (freq_n == _freq && freq_d == freq_d_orig)
+      return;
 
-   if (freq != mixer.freq) {
-      interpolate = true;
+   if (freq_d_orig != _den) {
+      Bit64u tmp = (Bit64u)freq_f * (Bit64u)_den * (Bit64u)mixer.freq;
+      freq_f = (unsigned int)(tmp / (Bit64u)freq_d_orig);
+   }
+
+   freq_n = _freq;
+   freq_d = _den * mixer.freq;
+   freq_d_orig = _den;
+}
+
+void MixerChannel::EndFrame(Bitu samples) {
+   rend_n = rend_d = 0;
+   if (msbuffer_o <= samples) {
+      msbuffer_o = 0;
+      msbuffer_i = 0;
    }
    else {
-      interpolate = false;
+      msbuffer_o -= samples;
+      if (msbuffer_i >= samples) msbuffer_i -= samples;
+      else msbuffer_i = 0;
+      memmove(&msbuffer[0][0],&msbuffer[samples][0],msbuffer_o*sizeof(Bit32s)*2/*stereo*/);
    }
+
+   last_sample_write -= (unsigned int)samples;
 }
 
-void MixerChannel::Mix(Bitu _needed) {
-   needed=_needed;
-   while (enabled && needed>done) {
-      Bitu left = (needed - done);
-      left *= freq_add;
-      left  = (left >> FREQ_SHIFT) + ((left & FREQ_MASK)!=0);
-      handler(left);
+void MixerChannel::Mix(Bitu whole,Bitu frac) {
+   unsigned int patience = 2;
+   Bitu upto;
+
+   if (whole <= rend_n) return;
+   // assert(whole <= mixer.samples_this_ms.w);
+   // assert(rend_n < mixer.samples_this_ms.w);
+   Bit32s *outptr = &mixer.work[mixer.work_in+rend_n][0];
+
+   if (!enabled) {
+      rend_n = whole;
+      rend_d = frac;
+      return;
+   }
+
+   // HACK: We iterate twice only because of the Sound Blaster emulation. No other emulation seems to need this.
+   rendering_to_n = whole;
+   rendering_to_d = frac;
+   while (msbuffer_o < whole) {
+      Bit64u todo = (Bit64u)(whole - msbuffer_o) * (Bit64u)freq_n;
+      todo += (Bit64u)freq_f;
+      todo += (Bit64u)freq_d - (Bit64u)1;
+      todo /= (Bit64u)freq_d;
+      if (!current_loaded) todo++;
+      handler(todo);
+
+      if (--patience == 0) break;
+   }
+
+   if (msbuffer_o < whole)
+      padFillSampleInterpolation(whole);
+
+   upto = whole;
+   if (upto > msbuffer_o) upto = msbuffer_o;
+
+   while (rend_n < whole && msbuffer_i < upto) {
+      *outptr++ += msbuffer[msbuffer_i][0];
+      *outptr++ += msbuffer[msbuffer_i][1];
+      msbuffer_i++;
+      rend_n++;
    }
+
+   rend_n = whole;
+   rend_d = frac;
 }
 
 void MixerChannel::AddSilence(void) {
-   if (done<needed) {
-      done=needed;
-      //Make sure the next samples are zero when they get switched to prev
-      nextSample[0] = 0;
-      nextSample[1] = 0;
-      //This should trigger an instant request for new samples
-      freq_counter = FREQ_NEXT;
-   }
 }
 
 template<class Type,bool stereo,bool signeddata,bool nativeorder>
-inline void MixerChannel::AddSamples(Bitu len, const Type* data) {
-   //Position where to write the data
-   Bitu mixpos = mixer.pos + done;
-   //Position in the incoming data
-   Bitu pos = 0;
-   //Mix and data for the full length
-   while (1) {
-      //Does new data need to get read?
-      while (freq_counter >= FREQ_NEXT) {
-         //Would this overflow the source data, then it's time to leave
-         if (pos >= len)
-            return;
-         freq_counter -= FREQ_NEXT;
-         prevSample[0] = nextSample[0];
-         if (stereo) {
-            prevSample[1] = nextSample[1];
-         }
-         if ( sizeof( Type) == 1) {
-            if (!signeddata) {
-               if (stereo) {
-                  nextSample[0]=(((Bit8s)(data[pos*2+0] ^ 0x80)) << 8);
-                  nextSample[1]=(((Bit8s)(data[pos*2+1] ^ 0x80)) << 8);
-               } else {
-                  nextSample[0]=(((Bit8s)(data[pos] ^ 0x80)) << 8);
-               }
-            } else {
-               if (stereo) {
-                  nextSample[0]=(data[pos*2+0] << 8);
-                  nextSample[1]=(data[pos*2+1] << 8);
-               } else {
-                  nextSample[0]=(data[pos] << 8);
-               }
-            }
-         //16bit and 32bit both contain 16bit data internally
-         } else  {
-            if (signeddata) {
-               if (stereo) {
-                  if (nativeorder) {
-                     nextSample[0]=data[pos*2+0];
-                     nextSample[1]=data[pos*2+1];
-                  } else {
-                     if ( sizeof( Type) == 2) {
-                        nextSample[0]=(Bit16s)host_readw((HostPt)&data[pos*2+0]);
-                        nextSample[1]=(Bit16s)host_readw((HostPt)&data[pos*2+1]);
-                     } else {
-                        nextSample[0]=(Bit32s)host_readd((HostPt)&data[pos*2+0]);
-                        nextSample[1]=(Bit32s)host_readd((HostPt)&data[pos*2+1]);
-                     }
-                  }
-               } else {
-                  if (nativeorder) {
-                     nextSample[0] = data[pos];
-                  } else {
-                     if ( sizeof( Type) == 2) {
-                        nextSample[0]=(Bit16s)host_readw((HostPt)&data[pos]);
-                     } else {
-                        nextSample[0]=(Bit32s)host_readd((HostPt)&data[pos]);
-                     }
-                  }
-               }
-            } else {
-               if (stereo) {
-                  if (nativeorder) {
-                     nextSample[0]=(Bits)data[pos*2+0]-32768;
-                     nextSample[1]=(Bits)data[pos*2+1]-32768;
-                  } else {
-                     if ( sizeof( Type) == 2) {
-                        nextSample[0]=(Bits)host_readw((HostPt)&data[pos*2+0])-32768;
-                        nextSample[1]=(Bits)host_readw((HostPt)&data[pos*2+1])-32768;
-                     } else {
-                        nextSample[0]=(Bits)host_readd((HostPt)&data[pos*2+0])-32768;
-                        nextSample[1]=(Bits)host_readd((HostPt)&data[pos*2+1])-32768;
-                     }
-                  }
-               } else {
-                  if (nativeorder) {
-                     nextSample[0]=(Bits)data[pos]-32768;
-                  } else {
-                     if ( sizeof( Type) == 2) {
-                        nextSample[0]=(Bits)host_readw((HostPt)&data[pos])-32768;
-                     } else {
-                        nextSample[0]=(Bits)host_readd((HostPt)&data[pos])-32768;
-                     }
-                  }
-               }
-            }
-         }
-         //This sample has been handled now, increase position
-         pos++;
+inline void MixerChannel::loadCurrentSample(Bitu &len, const Type* &data) {
+   last[0] = current[0];
+   last[1] = current[1];
+
+   if (sizeof(Type) == 1) {
+      const uint8_t xr = signeddata ? 0x00 : 0x80;
+
+      len--;
+      current[0] = ((Bit8s)((*data++) ^ xr)) << 8;
+      if (stereo)
+         current[1] = ((Bit8s)((*data++) ^ xr)) << 8;
+      else
+         current[1] = current[0];
+   }
+   else if (sizeof(Type) == 2) {
+      const uint16_t xr = signeddata ? 0x0000 : 0x8000;
+      uint16_t d;
+
+      len--;
+      if (nativeorder) d = ((Bit16u)((*data++) ^ xr));
+      else d = host_readw((HostPt)(data++)) ^ xr;
+      current[0] = (Bit16s)d;
+      if (stereo) {
+         if (nativeorder) d = ((Bit16u)((*data++) ^ xr));
+         else d = host_readw((HostPt)(data++)) ^ xr;
+         current[1] = (Bit16s)d;
       }
-      //Where to write
-      mixpos &= MIXER_BUFMASK;
-      Bit32s* write = mixer.work[mixpos];
-      if (!interpolate) {
-         write[0] += prevSample[0] * volmul[0];
-         write[1] += (stereo ? prevSample[1] : prevSample[0]) * volmul[1];
+      else {
+         current[1] = current[0];
+      }
+   }
+   else if (sizeof(Type) == 4) {
+      const uint32_t xr = signeddata ? 0x00000000UL : 0x80000000UL;
+      uint32_t d;
+
+      len--;
+      if (nativeorder) d = ((Bit32u)((*data++) ^ xr));
+      else d = host_readd((HostPt)(data++)) ^ xr;
+      current[0] = (Bit32s)d;
+      if (stereo) {
+         if (nativeorder) d = ((Bit32u)((*data++) ^ xr));
+         else d = host_readd((HostPt)(data++)) ^ xr;
+         current[1] = (Bit32s)d;
       }
       else {
-         Bits diff_mul = freq_counter & FREQ_MASK;
-         Bits sample = prevSample[0] + (((nextSample[0] - prevSample[0]) * diff_mul) >> FREQ_SHIFT);
-         write[0] += sample*volmul[0];
-         if (stereo) {
-            sample = prevSample[1] + (((nextSample[1] - prevSample[1]) * diff_mul) >> FREQ_SHIFT);
-         }
-         write[1] += sample*volmul[1];
+         current[1] = current[0];
+      }
+   }
+   else {
+      current[0] = current[1] = 0;
+      len = 0;
+   }
+
+   if (stereo) {
+      delta[0] = current[0] - last[0];
+      delta[1] = current[1] - last[1];
+   }
+   else {
+      delta[1] = delta[0] = current[0] - last[0];
+   }
+
+   current_loaded = true;
+}
+
+inline void MixerChannel::padFillSampleInterpolation(const Bitu upto) {
+   finishSampleInterpolation(upto);
+   if (msbuffer_o < upto) {
+      if (freq_f > freq_d) freq_f = freq_d; // this is an abrupt stop, so interpolation must not carry over, to help avoid popping artifacts
+
+      while (msbuffer_o < upto) {
+         msbuffer[msbuffer_o][0] = current[0];
+         msbuffer[msbuffer_o][1] = current[1];
+         msbuffer_o++;
       }
-      //Prepare for next sample
-      freq_counter += freq_add;
-      mixpos++;
-      done++;
    }
 }
 
-void MixerChannel::AddStretched(Bitu len,Bit16s * data) {
-   if (done >= needed) {
-      LOG_MSG("Can't add, buffer full");
+void MixerChannel::finishSampleInterpolation(const Bitu upto) {
+   if (!current_loaded) return;
+   runSampleInterpolation(upto);
+}
+
+double MixerChannel::timeSinceLastSample(void) {
+   Bits delta = (Bits)mixer.samples_rendered_ms.w - (Bits)last_sample_write;
+   return ((double)delta) / mixer.freq;
+}
+
+inline bool MixerChannel::runSampleInterpolation(const Bitu upto) {
+   int sample;
+
+   if (msbuffer_o >= upto)
+      return false;
+
+   current[0] = last[0] + delta[0];
+   current[1] = last[1] + delta[1];
+   while (freq_f < freq_d) {
+      msbuffer[msbuffer_o][0] = current[0] * volmul[0];
+      msbuffer[msbuffer_o][1] = current[1] * volmul[1];
+
+      freq_f += freq_n;
+      if ((++msbuffer_o) >= upto)
+         return false;
+   }
+
+   return true;
+}
+
+template<class Type,bool stereo,bool signeddata,bool nativeorder>
+inline void MixerChannel::AddSamples(Bitu len, const Type* data) {
+   last_sample_write = mixer.samples_rendered_ms.w;
+
+   if (msbuffer_o >= 2048) {
+      fprintf(stderr,"WARNING: addSample overrun (immediate)\n");
       return;
    }
-   //Target samples this inputs gets stretched into
-   Bitu outlen = needed - done;
-   Bitu index = 0;
-   Bitu index_add = (len << FREQ_SHIFT)/outlen;
-   Bitu mixpos = mixer.pos + done;
-   done = needed;
-   Bitu pos = 0;
-
-   while (outlen--) {
-      Bitu new_pos = index >> FREQ_SHIFT;
-      if (pos != new_pos) {
-         pos = new_pos;
-         //Forward the previous sample
-         prevSample[0] = data[0];
-         data++;
-      }
-      Bits diff = data[0] - prevSample[0];
-      Bits diff_mul = index & FREQ_MASK;
-      index += index_add;
-      mixpos &= MIXER_BUFMASK;
-      Bits sample = prevSample[0] + ((diff * diff_mul) >> FREQ_SHIFT);
-      mixer.work[mixpos][0] += sample * volmul[0];
-      mixer.work[mixpos][1] += sample * volmul[1];
-      mixpos++;
+
+   if (!current_loaded) {
+      if (len == 0) return;
+
+      loadCurrentSample<Type,stereo,signeddata,nativeorder>(len,data);
+      if (len == 0) {
+         freq_f = freq_d; /* encourage loading next round */
+         return;
+      }
+
+      loadCurrentSample<Type,stereo,signeddata,nativeorder>(len,data);
+      freq_f = 0; /* interpolate now from what we just loaded */
+   }
+
+   for (;;) {
+      if (freq_f >= freq_d) {
+         if (len == 0) break;
+         loadCurrentSample<Type,stereo,signeddata,nativeorder>(len,data);
+         freq_f -= freq_d;
+      }
+      if (!runSampleInterpolation(2048))
+         break;
    }
 }
 
@@ -381,195 +428,167 @@ void MixerChannel::AddSamples_s32_nonnat
    AddSamples<Bit32s,true,true,false>(len,data);
 }
 
-void MixerChannel::FillUp(void) {
-   SDL_LockAudio();
-   if (!enabled || done<mixer.done) {
-      SDL_UnlockAudio();
-      return;
-   }
-   float index=PIC_TickIndex();
-   Mix((Bitu)(index*mixer.needed));
-   SDL_UnlockAudio();
-}
-
 extern bool ticksLocked;
+
+#if 0//unused
 static inline bool Mixer_irq_important(void) {
    /* In some states correct timing of the irqs is more important then
     * non stuttering audo */
    return (ticksLocked || (CaptureState & (CAPTURE_WAVE|CAPTURE_VIDEO)));
 }
-
-static Bit32u calc_tickadd(Bit32u freq) {
-#if TICK_SHIFT >16
-   Bit64u freq64 = static_cast<Bit64u>(freq);
-   freq64 = (freq64<<TICK_SHIFT)/1000;
-   Bit32u r = static_cast<Bit32u>(freq64);
-   return r;
-#else
-   return (freq<<TICK_SHIFT)/1000;
 #endif
-}
 
-/* Mix a certain amount of new samples */
-static void MIXER_MixData(Bitu needed) {
-   MixerChannel * chan=mixer.channels;
+unsigned long long mixer_sample_counter = 0;
+double mixer_start_pic_time = 0;
+
+/* once a millisecond, render 1ms of audio, up to whole samples */
+static void MIXER_MixData(Bitu fracs/*render up to*/) {
+   unsigned int prev_rendered = mixer.samples_rendered_ms.w;
+   MixerChannel *chan = mixer.channels;
+   unsigned int whole,frac;
+   bool endframe = false;
+
+   if (fracs >= (Bitu)(mixer.samples_this_ms.w * mixer.samples_this_ms.fd)) {
+      fracs = (Bitu)(mixer.samples_this_ms.w * mixer.samples_this_ms.fd);
+      endframe = true;
+   }
+
+   whole = (unsigned int)(fracs / mixer.samples_this_ms.fd);
+   frac = (unsigned int)(fracs % mixer.samples_this_ms.fd);
+   if (whole <= mixer.samples_rendered_ms.w) return;
+
    while (chan) {
-      chan->Mix(needed);
+      chan->Mix(whole,fracs);
+      if (endframe) chan->EndFrame(mixer.samples_this_ms.w);
       chan=chan->next;
    }
+
    if (CaptureState & (CAPTURE_WAVE|CAPTURE_VIDEO)) {
+           Bit32s volscale1 = (Bit32s)(mixer.recordvol[0] * (1 << MIXER_VOLSHIFT));
+           Bit32s volscale2 = (Bit32s)(mixer.recordvol[1] * (1 << MIXER_VOLSHIFT));
       Bit16s convert[1024][2];
-      Bitu added=needed-mixer.done;
-      if (added>1024)
-         added=1024;
-      Bitu readpos=(mixer.pos+mixer.done)&MIXER_BUFMASK;
+      Bitu added = whole - prev_rendered;
+      if (added>1024) added=1024;
+      Bitu readpos = mixer.work_in + prev_rendered;
       for (Bitu i=0;i<added;i++) {
-         Bits sample=mixer.work[readpos][0] >> MIXER_VOLSHIFT;
-         convert[i][0]=MIXER_CLIP(sample);
-         sample=mixer.work[readpos][1] >> MIXER_VOLSHIFT;
-         convert[i][1]=MIXER_CLIP(sample);
-         readpos=(readpos+1)&MIXER_BUFMASK;
+               convert[i][0]=MIXER_CLIP(((Bit64s)mixer.work[readpos][0] * (Bit64s)volscale1) >> (MIXER_VOLSHIFT + MIXER_VOLSHIFT));
+               convert[i][1]=MIXER_CLIP(((Bit64s)mixer.work[readpos][1] * (Bit64s)volscale2) >> (MIXER_VOLSHIFT + MIXER_VOLSHIFT));
+         readpos++;
       }
+      // assert(readpos <= MIXER_BUFSIZE);
       CAPTURE_AddWave( mixer.freq, added, (Bit16s*)convert );
    }
-   //Reset the the tick_add for constant speed
-   if( Mixer_irq_important() )
-      mixer.tick_add = calc_tickadd(mixer.freq);
-   mixer.done = needed;
+
+   mixer.samples_rendered_ms.w = whole;
+   mixer.samples_rendered_ms.fd = frac;
+   mixer_sample_counter += mixer.samples_rendered_ms.w - prev_rendered;
 }
 
-static void MIXER_Mix(void) {
+static void MIXER_FillUp(void) {
    SDL_LockAudio();
-   MIXER_MixData(mixer.needed);
-   mixer.tick_counter += mixer.tick_add;
-   mixer.needed+=(mixer.tick_counter >> TICK_SHIFT);
-   mixer.tick_counter &= TICK_MASK;
+   float index = PIC_TickIndex();
+   if (index < 0) index = 0;
+   MIXER_MixData((Bitu)(index * ((Bitu)mixer.samples_this_ms.w * (Bitu)mixer.samples_this_ms.fd)));
    SDL_UnlockAudio();
 }
 
-static void MIXER_Mix_NoSound(void) {
-   MIXER_MixData(mixer.needed);
-   /* Clear piece we've just generated */
-   for (Bitu i=0;i<mixer.needed;i++) {
-      mixer.work[mixer.pos][0]=0;
-      mixer.work[mixer.pos][1]=0;
-      mixer.pos=(mixer.pos+1)&MIXER_BUFMASK;
-   }
-   /* Reduce count in channels */
-   for (MixerChannel * chan=mixer.channels;chan;chan=chan->next) {
-      if (chan->done>mixer.needed) chan->done-=mixer.needed;
-      else chan->done=0;
-   }
-   /* Set values for next tick */
-   mixer.tick_counter += mixer.tick_add;
-   mixer.needed = (mixer.tick_counter >> TICK_SHIFT);
-   mixer.tick_counter &= TICK_MASK;
-   mixer.done=0;
+void MixerChannel::FillUp(void) {
+   MIXER_FillUp();
 }
 
-static void SDLCALL MIXER_CallBack(void * userdata, Uint8 *stream, int len) {
-   Bitu need=(Bitu)len/MIXER_SSIZE;
-   Bit16s * output=(Bit16s *)stream;
-   Bitu reduce;
-   Bitu pos;
-   //Local resampling counter to manipulate the data when sending it off to the callback
-   Bitu index, index_add;
-   Bits sample;
-   /* Enough room in the buffer ? */
-   if (mixer.done < need) {
-//      LOG_MSG("Full underrun need %d, have %d, min %d", need, mixer.done, mixer.min_needed);
-      if((need - mixer.done) > (need >>7) ) //Max 1 procent stretch.
-         return;
-      reduce = mixer.done;
-      index_add = (reduce << TICK_SHIFT) / need;
-      mixer.tick_add = calc_tickadd(mixer.freq+mixer.min_needed);
-   } else if (mixer.done < mixer.max_needed) {
-      Bitu left = mixer.done - need;
-      if (left < mixer.min_needed) {
-         if( !Mixer_irq_important() ) {
-            Bitu needed = mixer.needed - need;
-            Bitu diff = (mixer.min_needed>needed?mixer.min_needed:needed) - left;
-            mixer.tick_add = calc_tickadd(mixer.freq+(diff*3));
-            left = 0; //No stretching as we compensate with the tick_add value
-         } else {
-            left = (mixer.min_needed - left);
-            left = 1 + (2*left) / mixer.min_needed; //left=1,2,3
-         }
-//         LOG_MSG("needed underrun need %d, have %d, min %d, left %d", need, mixer.done, mixer.min_needed, left);
-         reduce = need - left;
-         index_add = (reduce << TICK_SHIFT) / need;
-      } else {
-         reduce = need;
-         index_add = (1 << TICK_SHIFT);
-//         LOG_MSG("regular run need %d, have %d, min %d, left %d", need, mixer.done, mixer.min_needed, left);
-
-         /* Mixer tick value being updated:
-          * 3 cases:
-          * 1) A lot too high. >division by 5. but maxed by 2* min to prevent too fast drops.
-          * 2) A little too high > division by 8
-          * 3) A little to nothing above the min_needed buffer > go to default value
-          */
-         Bitu diff = left - mixer.min_needed;
-         if(diff > (mixer.min_needed<<1)) diff = mixer.min_needed<<1;
-         if(diff > (mixer.min_needed>>1))
-            mixer.tick_add = calc_tickadd(mixer.freq-(diff/5));
-         else if (diff > (mixer.min_needed>>2))
-            mixer.tick_add = calc_tickadd(mixer.freq-(diff>>3));
-         else
-            mixer.tick_add = calc_tickadd(mixer.freq);
-      }
-   } else {
-      /* There is way too much data in the buffer */
-//      LOG_MSG("overflow run need %d, have %d, min %d", need, mixer.done, mixer.min_needed);
-      if (mixer.done > MIXER_BUFSIZE)
-         index_add = MIXER_BUFSIZE - 2*mixer.min_needed;
-      else
-         index_add = mixer.done - 2*mixer.min_needed;
-      index_add = (index_add << TICK_SHIFT) / need;
-      reduce = mixer.done - 2* mixer.min_needed;
-      mixer.tick_add = calc_tickadd(mixer.freq-(mixer.min_needed/5));
-   }
-   /* Reduce done count in all channels */
-   for (MixerChannel * chan=mixer.channels;chan;chan=chan->next) {
-      if (chan->done>reduce) chan->done-=reduce;
-      else chan->done=0;
-   }
-
-   // Reset mixer.tick_add when irqs are important
-   if( Mixer_irq_important() )
-      mixer.tick_add = calc_tickadd(mixer.freq);
-
-   mixer.done -= reduce;
-   mixer.needed -= reduce;
-   pos = mixer.pos;
-   mixer.pos = (mixer.pos + reduce) & MIXER_BUFMASK;
-   index = 0;
-   if(need != reduce) {
-      while (need--) {
-         Bitu i = (pos + (index >> TICK_SHIFT )) & MIXER_BUFMASK;
-         index += index_add;
-         sample=mixer.work[i][0]>>MIXER_VOLSHIFT;
-         *output++=MIXER_CLIP(sample);
-         sample=mixer.work[i][1]>>MIXER_VOLSHIFT;
-         *output++=MIXER_CLIP(sample);
-      }
-      /* Clean the used buffer */
-      while (reduce--) {
-         pos &= MIXER_BUFMASK;
-         mixer.work[pos][0]=0;
-         mixer.work[pos][1]=0;
-         pos++;
-      }
-   } else {
-      while (reduce--) {
-         pos &= MIXER_BUFMASK;
-         sample=mixer.work[pos][0]>>MIXER_VOLSHIFT;
-         *output++=MIXER_CLIP(sample);
-         sample=mixer.work[pos][1]>>MIXER_VOLSHIFT;
-         *output++=MIXER_CLIP(sample);
-         mixer.work[pos][0]=0;
-         mixer.work[pos][1]=0;
-         pos++;
+static void MIXER_Mix(void) {
+   Bitu thr;
+
+   SDL_LockAudio();
+
+   /* render */
+   // assert((mixer.work_in+mixer.samples_per_ms.w) <= MIXER_BUFSIZE);
+   MIXER_MixData((Bitu)mixer.samples_this_ms.w * (Bitu)mixer.samples_this_ms.fd);
+   mixer.work_in += mixer.samples_this_ms.w;
+
+   /* how many samples for the next ms? */
+   mixer.samples_this_ms.w = mixer.samples_per_ms.w;
+   mixer.samples_this_ms.fn += mixer.samples_per_ms.fn;
+   if (mixer.samples_this_ms.fn >= mixer.samples_this_ms.fd) {
+      mixer.samples_this_ms.fn -= mixer.samples_this_ms.fd;
+      mixer.samples_this_ms.w++;
+   }
+
+   /* advance. we use in/out & wrap pointers to make sure the rendering code
+    * doesn't have to worry about circular buffer wraparound. */
+   thr = mixer.blocksize;
+   if (thr < mixer.samples_this_ms.w) thr = mixer.samples_this_ms.w;
+   if ((mixer.work_in+thr) > MIXER_BUFSIZE) {
+      mixer.work_wrap = mixer.work_in;
+      mixer.work_in = 0;
+   }
+   // assert((mixer.work_in+thr) <= MIXER_BUFSIZE);
+   // assert((mixer.work_in+mixer.samples_this_ms.w) <= MIXER_BUFSIZE);
+   memset(&mixer.work[mixer.work_in][0],0,sizeof(Bit32s)*2*mixer.samples_this_ms.w);
+   mixer.samples_rendered_ms.fn = 0;
+   mixer.samples_rendered_ms.w = 0;
+   SDL_UnlockAudio();
+   MIXER_FillUp();
+}
+
+static void MIXER_CallBack(void * userdata, Uint8 *stream, int len) {
+   (void)userdata;//UNUSED
+       Bit32s volscale1 = (Bit32s)(mixer.mastervol[0] * (1 << MIXER_VOLSHIFT));
+       Bit32s volscale2 = (Bit32s)(mixer.mastervol[1] * (1 << MIXER_VOLSHIFT));
+   Bitu need = (Bitu)len/MIXER_SSIZE;
+   Bit16s *output = (Bit16s*)stream;
+   int remains;
+   Bit32s *in;
+
+       if (mixer.prebuffer_wait) {
+           remains = (int)mixer.work_in - (int)mixer.work_out;
+      if (remains < 0) remains += (int)mixer.work_wrap;
+           if (remains < 0) remains = 0;
+
+           if ((unsigned int)remains >= mixer.prebuffer_samples)
+                  mixer.prebuffer_wait = false;
+       }
+
+       if (!mixer.prebuffer_wait) {
+           in = &mixer.work[mixer.work_out][0];
+           while (need > 0) {
+                  if (mixer.work_out == mixer.work_in) break;
+                  *output++ = MIXER_CLIP((((Bit64s)(*in++)) * (Bit64s)volscale1) >> (MIXER_VOLSHIFT + MIXER_VOLSHIFT));
+                  *output++ = MIXER_CLIP((((Bit64s)(*in++)) * (Bit64s)volscale2) >> (MIXER_VOLSHIFT + MIXER_VOLSHIFT));
+                  mixer.work_out++;
+                  if (mixer.work_out >= mixer.work_wrap) {
+                      mixer.work_out = 0;
+                   in = &mixer.work[mixer.work_out][0];
+                  }
+               need--;
+           }
+       }
+
+       if (need > 0)
+           mixer.prebuffer_wait = true;
+
+   while (need > 0) {
+      *output++ = 0;
+      *output++ = 0;
+      need--;
+   }
+
+   remains = (int)mixer.work_in - (int)mixer.work_out;
+   if (remains < 0) remains += (int)mixer.work_wrap;
+
+   if ((unsigned long)remains >= (mixer.blocksize*2UL)) {
+      /* drop some samples to keep time */
+      unsigned int drop;
+
+      if ((unsigned long)remains >= (mixer.blocksize*3UL)) // hard drop
+         drop = ((unsigned int)remains - (unsigned int)(mixer.blocksize));
+      else // subtle drop
+         drop = (((unsigned int)remains - (unsigned int)(mixer.blocksize*2)) / 50U) + 1;
+
+      while (drop > 0) {
+         mixer.work_out++;
+         if (mixer.work_out >= mixer.work_wrap) mixer.work_out = 0;
+         drop--;
       }
    }
 }
@@ -612,6 +631,9 @@ public:
       if (cmd->FindString("MASTER",temp_line,false)) {
          MakeVolume((char *)temp_line.c_str(),mixer.mastervol[0],mixer.mastervol[1]);
       }
+           if (cmd->FindString("RECORD",temp_line,false)) {
+                  MakeVolume((char *)temp_line.c_str(),mixer.recordvol[0],mixer.recordvol[1]);
+           }
       MixerChannel * chan=mixer.channels;
       while (chan) {
          if (cmd->FindString(chan->name,temp_line,false)) {
@@ -624,6 +646,7 @@ public:
       chan=mixer.channels;
       WriteOut("Channel  Main    Main(dB)\n");
       ShowVolume("MASTER",mixer.mastervol[0],mixer.mastervol[1]);
+      ShowVolume("RECORD",mixer.recordvol[0],mixer.recordvol[1]);
       for (chan=mixer.channels;chan;chan=chan->next)
          ShowVolume(chan->name,chan->volmain[0],chan->volmain[1]);
    }
@@ -673,12 +696,16 @@ void MIXER_Init(Section* sec) {
    mixer.blocksize=section->Get_int("blocksize");
 
    /* Initialize the internal stuff */
+       mixer.prebuffer_samples=0;
+       mixer.prebuffer_wait=true;
    mixer.channels=0;
    mixer.pos=0;
    mixer.done=0;
    memset(mixer.work,0,sizeof(mixer.work));
    mixer.mastervol[0]=1.0f;
    mixer.mastervol[1]=1.0f;
+   mixer.recordvol[0]=1.0f;
+   mixer.recordvol[1]=1.0f;
 
    /* Start the Mixer using SDL Sound at 22 khz */
    SDL_AudioSpec spec;
@@ -691,29 +718,48 @@ void MIXER_Init(Section* sec) {
    spec.userdata=NULL;
    spec.samples=(Uint16)mixer.blocksize;
 
-   mixer.tick_counter=0;
    if (mixer.nosound) {
       LOG_MSG("MIXER: No Sound Mode Selected.");
-      mixer.tick_add=calc_tickadd(mixer.freq);
-      TIMER_AddTickHandler(MIXER_Mix_NoSound);
+      TIMER_AddTickHandler(MIXER_Mix);
    } else if (SDL_OpenAudio(&spec, &obtained) <0 ) {
       mixer.nosound = true;
       LOG_MSG("MIXER: Can't open audio: %s , running in nosound mode.",SDL_GetError());
-      mixer.tick_add=calc_tickadd(mixer.freq);
-      TIMER_AddTickHandler(MIXER_Mix_NoSound);
+      TIMER_AddTickHandler(MIXER_Mix);
    } else {
       if((mixer.freq != obtained.freq) || (mixer.blocksize != obtained.samples))
          LOG_MSG("MIXER: Got different values from SDL: freq %d, blocksize %d",obtained.freq,obtained.samples);
+
       mixer.freq=obtained.freq;
       mixer.blocksize=obtained.samples;
-      mixer.tick_add=calc_tickadd(mixer.freq);
       TIMER_AddTickHandler(MIXER_Mix);
       SDL_PauseAudio(0);
    }
-   mixer.min_needed=section->Get_int("prebuffer");
-   if (mixer.min_needed>100) mixer.min_needed=100;
-   mixer.min_needed=(mixer.freq*mixer.min_needed)/1000;
-   mixer.max_needed=mixer.blocksize * 2 + 2*mixer.min_needed;
-   mixer.needed=mixer.min_needed+1;
+   mixer_start_pic_time = PIC_FullIndex();
+   mixer_sample_counter = 0;
+   mixer.work_in = mixer.work_out = 0;
+   mixer.work_wrap = MIXER_BUFSIZE;
+   if (mixer.work_wrap <= mixer.blocksize) E_Exit("blocksize too large");
+
+       {
+           int ms = section->Get_int("prebuffer");
+
+           if (ms < 0) ms = 20;
+
+           mixer.prebuffer_samples = ((unsigned int)ms * (unsigned int)mixer.freq) / 1000u;
+           if (mixer.prebuffer_samples > (mixer.work_wrap / 2))
+               mixer.prebuffer_samples = (mixer.work_wrap / 2);
+       }
+
+   // how many samples per millisecond? compute as improper fraction (sample rate / 1000)
+   mixer.samples_per_ms.w = mixer.freq / 1000U;
+   mixer.samples_per_ms.fn = mixer.freq % 1000U;
+   mixer.samples_per_ms.fd = 1000U;
+   mixer.samples_this_ms.w = mixer.samples_per_ms.w;
+   mixer.samples_this_ms.fn = 0;
+   mixer.samples_this_ms.fd = mixer.samples_per_ms.fd;
+   mixer.samples_rendered_ms.w = 0;
+   mixer.samples_rendered_ms.fn = 0;
+   mixer.samples_rendered_ms.fd = mixer.samples_per_ms.fd;
+
    PROGRAMS_MakeFile("MIXER.COM",MIXER_ProgramStart);
 }
diff -rupN dosbox-svn/src/hardware/sblaster.cpp dosbox-mixer//src/hardware/sblaster.cpp
--- dosbox-svn/src/hardware/sblaster.cpp
+++ dosbox-mixer//src/hardware/sblaster.cpp
@@ -1174,6 +1174,7 @@ static float calc_vol(Bit8u amount) {
 }
 static void CTMIXER_UpdateVolumes(void) {
    if (!sb.mixer.enabled) return;
+   sb.chan->FillUp();
    MixerChannel * chan;
    float m0 = calc_vol(sb.mixer.master[0]);
    float m1 = calc_vol(sb.mixer.master[1]);
[/code]
hail-to-the-ryzen
Member
 
Posts: 256
Joined: 2017-3-09 @ 01:34

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-29 @ 03:25

Just to confirm with the rest of you guys... you want the entire DOSBox-X mixer code in SVN?
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby Qbix » 2018-12-29 @ 09:41

For the mixer, I would have the check it first. As you are aware, it is rather important for fluency and interacts indirectly with the auto cycles behavior.
mda, which games would benefit from that ?

I rarely add new options to the configuration file, please be aware of that ;) Which games need the always scanline emulation and what is the drawback of having it on always ?
(X% slower ?)
Water flows down the stream
How to ask questions the smart way!
User avatar
Qbix
DOSBox Author
 
Posts: 10739
Joined: 2002-11-27 @ 14:50
Location: Fryslan

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-29 @ 10:04

Qbix wrote:For the mixer, I would have the check it first. As you are aware, it is rather important for fluency and interacts indirectly with the auto cycles behavior.
mda, which games would benefit from that ?


Go ahead and check it, then. The mixer rewrite in DOSBox-X was meant to strictly tie audio output to emulator time, which also solves a problem I noticed with AVI captures where the audio/video can drift out of sync (explained below). It also gave me an opportunity to add lowpass filters and "slew rate" interpolation to help Sound Blaster emulation match what the actual hardware sounds like (at least the SB 2.0 and SB Pro cards I have on hand).

To explain the A/V sync issue, I noticed with DOSBox SVN, as well as DOSBox-X up until the rewrite, that if you recorded a demo or game with the video capture program, that the audio and video would slowly drift out of sync. The reason is that "44.1KHz" on a sound card isn't exactly 44.1KHz, it can vary from 44.097KHz to 44.103Khz. At the same time, SDL_GetTicks() reads from the system clock, or performance counters, or any other time source that is not the sound card. So A/V sync would drift because DOSBox is running emulator time against SDL_GetTicks() while letting the sound card dictate how much audio to render.

DOSBox-X's mixer code solves that by tying audio output strictly to emulator time. Ticks in DOSBox are 1ms long, which is why the code is willing to accurately count out (as integer fractions) the number of samples per 1ms to keep time with a fractional part since 44.1KHz isn't evenly divisible by 1KHz.

This also allows the FillUp calls in the mixer to accurately pinpoint up to what point (in the 1ms) to render audio. This allows some emulation including Adlib, Tandy 3-voice, and Sound Blaster, to very accurately render state changes without causing too much overhead. There are DOS games that have hacks to play digitized speech/audio over Adlib (Pinball Dreams, Star Control II), and Tandy 3-voice (Skate or Die) that will render a muddy low-fi rendition without that.

The lowpass and "slew rate" filtering is used to emulate older versions of Sound Blaster hardware that lack high quality filters. In fact based on the SB 2.0 and SB Pro cards (and Pro Audio Spectrum 16), those old cards don't really have any filtering after the DAC. That means when the DSP chip puts out the next sample, the output changes between samples are more square like a square wave, though not perfectly since electronics have an intermediate change period (the slew). This is why low sample rates on SB 2.0 and SB Pro cards sound so "grungy and metallic". The SB Pro tries to minimally cover it a bit by offering a lowpass filter (and bypass bit in the mixer registers), but it's really just a 4KHz lowpass that you can turn on or off.

As for the MDA option, the DOSBox-X philosophy is to offer Hercules mode trimmed down to MDA only I/O so that you can use it to see how well (or even IF) the game detects MDA vs Hercules. If you're doing retro-development, the option is a good way to test whether your code can detect MDA vs Hercules before trying it on actual hardware to help reduce the compile + copy to floppy + run development cycle. If MDA-only emulation isn't needed or wanted by DOSBox SVN, that's perfectly fine with me.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby TheGreatCodeholio » 2018-12-30 @ 07:07

If any patches I post here are accepted or rejected, please let me know so that I can log it and work on patches you guys would like.

Is there any interest in incorporating the menu framework from DOSBox-X? It's cross-platform with support for Windows and Mac OS X menus, or it can draw it's own menus.
DOSBox-X project: more emulation better accuracy.
DOSLIB and DOSLIB2: Learn how to tinker and hack hardware and software from DOS.
User avatar
TheGreatCodeholio
Oldbie
 
Posts: 628
Joined: 2011-8-18 @ 20:15
Location: Seattle, WA

Re: DOSBox-X branch

Postby Scali » 2018-12-30 @ 13:01

TheGreatCodeholio wrote:To explain the A/V sync issue, I noticed with DOSBox SVN, as well as DOSBox-X up until the rewrite, that if you recorded a demo or game with the video capture program, that the audio and video would slowly drift out of sync. The reason is that "44.1KHz" on a sound card isn't exactly 44.1KHz, it can vary from 44.097KHz to 44.103Khz. At the same time, SDL_GetTicks() reads from the system clock, or performance counters, or any other time source that is not the sound card. So A/V sync would drift because DOSBox is running emulator time against SDL_GetTicks() while letting the sound card dictate how much audio to render.


Completely unrelated, but funny enough I ran into pretty much the exact same issue at work:
We have an application that plays videos in realtime, and outputs over a Blackmagic DeckLink SDI card.
Thing is, the video is decoded via an internal clock, while the DeckLink runs on its own clock (or one provided externally).
So when you output the samples from the video 1:1 to the DeckLink, it will output over SDI based on its own clock.
This means that your video decoder gets out of sync with the output over time, and either gets buffer underruns or overruns.

Same thing: needs a total rewrite. The video cannot be decoded in realtime, but has to decode 'on demand', and provide timestamps for each audio and video frame. Then these timestamps need to be synced ONLY to the clock of the DeckLink.

TheGreatCodeholio wrote:This also allows the FillUp calls in the mixer to accurately pinpoint up to what point (in the 1ms) to render audio. This allows some emulation including Adlib, Tandy 3-voice, and Sound Blaster, to very accurately render state changes without causing too much overhead. There are DOS games that have hacks to play digitized speech/audio over Adlib (Pinball Dreams, Star Control II), and Tandy 3-voice (Skate or Die) that will render a muddy low-fi rendition without that.


Yea, that works great.
I can add that this sample-playing trick is also used on CMS/GameBlaster in at least one game: BattleTech: The Crescent Hawks' Revenge

TheGreatCodeholio wrote:As for the MDA option, the DOSBox-X philosophy is to offer Hercules mode trimmed down to MDA only I/O so that you can use it to see how well (or even IF) the game detects MDA vs Hercules. If you're doing retro-development, the option is a good way to test whether your code can detect MDA vs Hercules before trying it on actual hardware to help reduce the compile + copy to floppy + run development cycle. If MDA-only emulation isn't needed or wanted by DOSBox SVN, that's perfectly fine with me.


It would be nice if MDA mode also emulates that MDA only has 4k of video memory (as opposed to the 32k/64k of Hercules).
MDA memory wraps that 4k around in the range of B0000-B7FFF, where Hercules has a full 32k there without wraparound, and another optional 32k from B8000-BFFFF when no CGA card is installed.
Scali
l33t
 
Posts: 3698
Joined: 2014-12-13 @ 14:24

PreviousNext

Return to DOSBox Patches

Who is online

Users browsing this forum: No registered users and 2 guests