git @ Cat's Eye Technologies linapple / master src / Speaker.cpp
master

Tree @master (Download .tar.gz)

Speaker.cpp @masterraw · history · blame

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
/*
AppleWin : An Apple //e emulator for Windows

Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2007, Tom Charlesworth, Michael Pohoreski

AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Description: Speaker emulation
 *
 * Author: Various
 */

/* Remake for SDL Audio for Linux (or other SDL-compliant OSes) by beom beotiger --bb */

#include "stdafx.h"
// for _ASSERT ion. (here _ASSERT means Unix(tm) assert) --bb
#include <assert.h>

// Notes:
//
// [OLD: 23.191 Apple CLKs == 44100Hz (CLK_6502/44100)]
// 23 Apple CLKS per PC sample (played back at 44.1KHz)
//
//
// The speaker's wave output drives how much 6502 emulation is done in real-time, eg:
// If the speaker's wave buffer is running out of sample-data, then more 6502 cycles
// need to be executed to top-up the wave buffer.
// This is in contrast to the AY8910 voices, which can simply generate more data if
// their buffers are running low.


// number of channels and buffer size for Apple][ Speakers
static const unsigned short g_nSPKR_NumChannels = 1;
//-------------------------------------

// Globals (SOUND_WAVE)
// GPH This "INIT" value is the actual sample data written to the speaker when
// it gets whapped with a BIT $C030.  Note the output is always digital.
// The Apple II had no notion of volume or of looping waves, only the single
// "click" of the speaker. We set this to a value well under the maximum to
// provide headroom for mixing Mockingboard sound without causing peak clipping.
const short    SPKR_DATA_INIT = (short)0x2000;  // data written to speakers buffer

static short  g_nSpeakerData  = 0x0000;   //SPKR_DATA_INIT;
static short  *g_pSpeakerBuffer = NULL;
static UINT      g_nBufferIdx  = 0;

static short  *g_pStereoBuffer = NULL;  // buffer for stereo samples

static short  *g_pRemainderBuffer = NULL;  // Remainder buffer
static UINT      g_nRemainderBufferSize;    // Setup in SpkrInitialize()
static UINT      g_nRemainderBufferIdx;    // Setup in SpkrInitialize()

// Application-wide globals:
DWORD        soundtype    = SOUND_WAVE; //default
double        g_fClksPerSpkrSample;    // Setup in SetClksPerSpkrSample()

// Globals
static unsigned __int64  g_nSpkrQuietCycleCount = 0;
static unsigned __int64 g_nSpkrLastCycle = 0;
static bool g_bSpkrToggleFlag = false;

static bool g_bSpkrAvailable = false;
static bool g_bSpkrRecentlyActive = false;

//-----------------------------------------------------------------------------

// Forward refs:
//ULONG   Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSamples);
static ULONG   Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples);
static void    Spkr_SetActive(bool bActive);

//=============================================================================
// Let us leave benchmark for the near future --bb ^_^
#if 0
static void DisplayBenchmarkResults ()
{

  DWORD totaltime = GetTickCount() - extbench;
  VideoRedrawScreen();
  TCHAR buffer[64];
  sprintf(buffer,
           TEXT("This benchmark took %u.%02u seconds."),
           (unsigned)(totaltime / 1000),
           (unsigned)((totaltime / 10) % 100));
/*  MessageBox(g_hFrameWindow,
             buffer,
             TEXT("Benchmark Results"),
             MB_ICONINFORMATION | MB_SETFOREGROUND);*/
  printf("This benchmark took %u.%02u seconds.",
     (unsigned)(totaltime / 1000), (unsigned)((totaltime / 10) % 100));

}
#endif

//=============================================================================

static void SetClksPerSpkrSample()
{
//  // 23.191 clks for 44.1Khz (when 6502 CLK=1.0Mhz)
//  g_fClksPerSpkrSample = g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE;

  // Use integer value: Better for MJ Mahon's RT.SYNTH.DSK (integer multiples of 1.023MHz Clk)
  // . 23 clks @ 1.023MHz    SPKR_SAMPLE_RATE = 44100Hz!?
  g_fClksPerSpkrSample = (double) (UINT) (g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE);
}

//=============================================================================

static void InitRemainderBuffer()
{
  delete [] g_pRemainderBuffer;

  SetClksPerSpkrSample();

  g_nRemainderBufferSize = (UINT) g_fClksPerSpkrSample;
  if ((double)g_nRemainderBufferSize != g_fClksPerSpkrSample)
    g_nRemainderBufferSize++;

  g_pRemainderBuffer = new short [g_nRemainderBufferSize];
  memset(g_pRemainderBuffer, 0, g_nRemainderBufferSize);

  g_nRemainderBufferIdx = 0;
}

//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//
//=============================================================================

void SpkrDestroy ()
{
  Spkr_DSUninit();
  if(soundtype == SOUND_WAVE)
  {
    delete [] g_pSpeakerBuffer;
    delete [] g_pStereoBuffer;
    delete [] g_pRemainderBuffer;

    g_pSpeakerBuffer   = NULL;
    g_pStereoBuffer    = NULL;
    g_pRemainderBuffer = NULL;
  }
}

//=============================================================================

void SpkrInitialize ()
{
  if(g_fh)
  {
    fprintf(g_fh, "Spkr Config: soundtype = %d ", (int)soundtype);
    switch(soundtype)
    {
      case SOUND_NONE:   fprintf(g_fh, "(NONE)\n"); break;
      case SOUND_WAVE:   fprintf(g_fh, "(WAVE)\n"); break;
      default:           fprintf(g_fh, "(UNDEFINED!)\n"); break;
    }
  }

  if(g_bDisableDirectSound)
  {
//    SpeakerVoice.bMute = true;
  }
  else
  {
    //DSInit();
    g_bSpkrAvailable = Spkr_DSInit();
  }

  if (soundtype == SOUND_WAVE)
  {
    InitRemainderBuffer();
     // Buffer can hold a max of 1 seconds worth of samples
    g_pSpeakerBuffer = new short [SPKR_SAMPLE_RATE];
    g_pStereoBuffer  = new short [SPKR_SAMPLE_RATE * 2]; // doubled for stereo
  }
}

//=============================================================================
// NB. Called when /g_fCurrentCLK6502/ changes
void SpkrReinitialize ()
{
  if (soundtype == SOUND_WAVE)
  {
    InitRemainderBuffer();
  }
}

//=============================================================================

void SpkrReset()
{
  g_nBufferIdx = 0;
  g_nSpkrQuietCycleCount = 0;
  g_bSpkrToggleFlag = false;

  InitRemainderBuffer();
  Spkr_SubmitWaveBuffer(NULL, 0);
  Spkr_SetActive(false);
  Spkr_Demute();
}

//=================2012 AD =========================================================
#if 0
BOOL SpkrSetEmulationType (DWORD newtype)
{
  if (soundtype != SOUND_NONE)
    SpkrDestroy();
  soundtype = newtype;
  if (soundtype != SOUND_NONE)
    SpkrInitialize();
  if (soundtype != newtype)
    switch (newtype) {  // some fault occured
      case SOUND_WAVE:
/*        MessageBox(window,
                   TEXT("The emulator is unable to initialize a waveform ")
                   TEXT("output device.  Make sure you have a sound card ")
                   TEXT("and a driver installed and that windows is ")
                   TEXT("correctly configured to use the driver.  Also ")
                   TEXT("ensure that no other program is currently using ")
                   TEXT("the device."),
                   TEXT("Configuration"),
                   MB_ICONEXCLAMATION | MB_SETFOREGROUND);*/
        // Need to tuck SDL_GetError() hhere? ------------------------------------------
        fprintf(stderr, "Unable to initialize a waveform output device.\n");
        return 0;

    }
  return 1;
}
#endif
//=============================================================================

static void ReinitRemainderBuffer(UINT nCyclesRemaining)
{
  if(nCyclesRemaining == 0)
    return;

  for(g_nRemainderBufferIdx=0; g_nRemainderBufferIdx<nCyclesRemaining; g_nRemainderBufferIdx++)
    g_pRemainderBuffer[g_nRemainderBufferIdx] = g_nSpeakerData;

  _ASSERT(g_nRemainderBufferIdx < g_nRemainderBufferSize);
}

static void UpdateRemainderBuffer(ULONG* pnCycleDiff)
{
  if(g_nRemainderBufferIdx)
  {
    while((g_nRemainderBufferIdx < g_nRemainderBufferSize) && *pnCycleDiff)
    {
      g_pRemainderBuffer[g_nRemainderBufferIdx] = g_nSpeakerData;
      g_nRemainderBufferIdx++;
      (*pnCycleDiff)--;
    }

    if(g_nRemainderBufferIdx == g_nRemainderBufferSize)
    {
      g_nRemainderBufferIdx = 0;
      signed long nSampleMean = 0;
      for(UINT i=0; i<g_nRemainderBufferSize; i++)
        nSampleMean += (signed long) g_pRemainderBuffer[i];
      nSampleMean /= (signed long) g_nRemainderBufferSize;

      if(g_nBufferIdx < SPKR_SAMPLE_RATE-1)
        g_pSpeakerBuffer[g_nBufferIdx++] = (short) nSampleMean;
    }
  }
}


static void UpdateSpkr()
{
  if(!g_bFullSpeed /*|| SoundCore_GetTimerState()*/)
  {
    ULONG nCycleDiff = (ULONG) (g_nCumulativeCycles - g_nSpkrLastCycle);

    UpdateRemainderBuffer(&nCycleDiff);

    ULONG nNumSamples = (ULONG) ((double)nCycleDiff / g_fClksPerSpkrSample);

    ULONG nCyclesRemaining = (ULONG) ((double)nCycleDiff - (double)nNumSamples * g_fClksPerSpkrSample);

    while((nNumSamples--) && (g_nBufferIdx < SPKR_SAMPLE_RATE-1)) {
      g_pSpeakerBuffer[g_nBufferIdx++] = g_nSpeakerData;
          }
    ReinitRemainderBuffer(nCyclesRemaining);  // Partially fill 1Mhz sample buffer
  }

  g_nSpkrLastCycle = g_nCumulativeCycles;

  // GPH Added - simulate decoupling capacitor - use approximate value
  //g_nSpeakerData = (short)((double) g_nSpeakerData * 0.995);
}

//=============================================================================

// Called by emulation code when Speaker I/O reg (0xC030) is accessed
BYTE SpkrToggle (WORD, WORD, BYTE, BYTE, ULONG nCyclesLeft)
{
  g_bSpkrToggleFlag = true;

  if(!g_bFullSpeed)
  Spkr_SetActive(true);

  needsprecision = cumulativecycles;  // ?

//   if (extbench)
//   {
//     DisplayBenchmarkResults();
//     extbench = 0;
//   }

  if (soundtype == SOUND_WAVE)
  {
    CpuCalcCycles(nCyclesLeft);

    UpdateSpkr();

    g_nSpeakerData = g_nSpeakerData ^ SPKR_DATA_INIT; //~g_nSpeakerData;
//        printf("SpkrToggle: %04x\n",g_nSpeakerData);
  }

  return MemReadFloatingBus(nCyclesLeft); // reading from $C030..$C03F retrurns unpredictable value?
}

//=============================================================================

// Called by ContinueExecution()
void SpkrUpdate (DWORD totalcycles)
{
  if(!g_bSpkrToggleFlag)
  {
    if(!g_nSpkrQuietCycleCount)
    {
      g_nSpkrQuietCycleCount = g_nCumulativeCycles;
    }
    else if(g_nCumulativeCycles - g_nSpkrQuietCycleCount > (unsigned __int64)g_fCurrentCLK6502/5)
    {
      // After 0.2 sec of Apple time, deactivate spkr voice
      // . This allows emulator to auto-switch to full-speed g_nAppMode for fast disk access
      Spkr_SetActive(false);
    }
  }
  else
  {
      g_nSpkrQuietCycleCount = 0;
      g_bSpkrToggleFlag = false;
  }

  if (soundtype == SOUND_WAVE)
  {
    UpdateSpkr();
    ULONG nSamplesUsed;

    if(g_bFullSpeed) g_nBufferIdx = 0;  // try this --bb
//      nSamplesUsed = Spkr_SubmitWaveBuffer/*_FullSpeed*/(g_pSpeakerBuffer, g_nBufferIdx);
    else {
      nSamplesUsed = Spkr_SubmitWaveBuffer(g_pSpeakerBuffer, g_nBufferIdx);

      _ASSERT(nSamplesUsed <= g_nBufferIdx);
      if(nSamplesUsed == 0) return;
      memmove(g_pSpeakerBuffer, &g_pSpeakerBuffer[nSamplesUsed], g_nBufferIdx-nSamplesUsed);
      g_nBufferIdx -= nSamplesUsed;
    }
  }
}

//=============================================================================

//-----------------------------------------------------------------------------
static ULONG Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples)
{
  // submit nNumSamples (== 2bytes long each (sizeof short))??
  // from pSpeakerBuffer to pDSSpkrBuf for callback DSPlaySnd

//  if(!g_bSpkrRecentlyActive) return nNumSamples;//if not active, just return?
  if(pSpeakerBuffer == NULL)
  {
  // just init sound buffer and cursors??
    return 0;
  }

    // Convert mono Speakers sounds to stereo (mainly for Mockingboard support)
  UINT len = nNumSamples * 2;  // stereo = 2 * mono
  UINT i;

  for(i = 0; i < len; i += 2)
    {
    g_pStereoBuffer[i] = g_pStereoBuffer[i + 1] = pSpeakerBuffer[i >> 1];
    }
// use code from OpenMSX
//  DSUploadBuffer(pSpeakerBuffer, nNumSamples);



  DSUploadBuffer(g_pStereoBuffer, len);  // submit stereo wave data
  return nNumSamples;  // always return as if we've filled everything!? --bb
}


///////////// Mute - set volume to MINIMUM,  Demute - set volume to NORMAL STATE? -bb
void Spkr_Mute()
{
  SDL_PauseAudio(1);  // dangerous functiouse - will mute Mockingboard, too. Need to be changed
}

void Spkr_Demute()
{
  SDL_PauseAudio(0);
}

//-----------------------------------------------------------------------------


static void Spkr_SetActive(bool bActive)
{    // yes, I know the right way is:   g_bSpkrRecentlyActive = bActive;, but... ^_^ --bb
  if(bActive)
  {
    // Called by SpkrToggle() or SpkrReset()
    g_bSpkrRecentlyActive = true;

  }
  else
  {
    // Called by SpkrUpdate() after 0.2s of speaker inactivity
    g_bSpkrRecentlyActive = false;
  }
}

bool Spkr_IsActive()
{
  return g_bSpkrRecentlyActive;
}

//-----------------------------------------------------------------------------
// How to deal with volume in SDL Audio
// may be need to go to SDL Mixer?
DWORD SpkrGetVolume()
{
//  return SpeakerVoice.dwUserVolume;
 return 0;
}

void SpkrSetVolume(DWORD dwVolume, DWORD dwVolumeMax)
{
/*  SpeakerVoice.dwUserVolume = dwVolume;

  SpeakerVoice.nVolume = NewVolume(dwVolume, dwVolumeMax);

  if(SpeakerVoice.bActive)
    SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.nVolume);*/
}

//=============================================================================

bool Spkr_DSInit()
{
  //
  // Create single Apple speaker voice
  //
  if(!g_bDSAvailable)
    return false;  // do not have DirectSound? Sorry, SDL Audio! ^_^   --bb
  return true;

}

void Spkr_DSUninit()
{

}

//=============================================================================

DWORD SpkrGetSnapshot(SS_IO_Speaker* pSS)
{
  pSS->g_nSpkrLastCycle = g_nSpkrLastCycle;
  return 0;
}

DWORD SpkrSetSnapshot(SS_IO_Speaker* pSS)
{
  g_nSpkrLastCycle = pSS->g_nSpkrLastCycle;
  return 0;
}