|
|
1.1 root 1: /*
2: Hatari - dmaSnd.c
3:
4: This file is distributed under the GNU Public License, version 2 or at
5: your option any later version. Read the file gpl.txt for details.
6:
7: STE DMA sound emulation. Does not seem to be very hard at first glance,
8: but since the DMA sound has to be mixed together with the PSG sound and
9: the output frequency of the host computer differs from the DMA sound
10: frequency, the copy function is a little bit complicated.
11: For reducing copy latency, we set a "interrupt" with Int_AddRelativeInterrupt
12: to occur just after a sound frame should be finished. There we update the
13: sound. The update function also triggers the ST interrupts (Timer A and
14: MFP-i7) which are often used in ST programs for setting a new sound frame
15: after the old one has finished.
16:
17: The microwire interface is not emulated (yet).
18:
19: Hardware I/O registers:
20:
21: $FF8900 (word) : DMA sound control register
22: $FF8903 (byte) : Frame Start Hi
23: $FF8905 (byte) : Frame Start Mi
24: $FF8907 (byte) : Frame Start Lo
25: $FF8909 (byte) : Frame Count Hi
26: $FF890B (byte) : Frame Count Mi
27: $FF890D (byte) : Frame Count Lo
28: $FF890F (byte) : Frame End Hi
29: $FF8911 (byte) : Frame End Mi
30: $FF8913 (byte) : Frame End Lo
31: $FF8920 (word) : Sound Mode Control (frequency, mono/stereo)
32: $FF8922 (byte) : Microwire Data Register
33: $FF8924 (byte) : Microwire Mask Register
34: */
1.1.1.5 ! root 35: const char DmaSnd_fileid[] = "Hatari dmaSnd.c : " __DATE__ " " __TIME__;
1.1 root 36:
37: #include "main.h"
38: #include "audio.h"
1.1.1.3 root 39: #include "configuration.h"
1.1 root 40: #include "dmaSnd.h"
41: #include "int.h"
42: #include "ioMem.h"
43: #include "memorySnapShot.h"
44: #include "mfp.h"
45: #include "sound.h"
1.1.1.3 root 46: #include "stMemory.h"
1.1 root 47:
48:
49: Uint16 nDmaSoundControl; /* Sound control register */
50:
1.1.1.2 root 51: static Uint16 nDmaSoundMode; /* Sound mode register */
1.1 root 52: static Uint16 nMicrowireData; /* Microwire Data register */
53: static Uint16 nMicrowireMask; /* Microwire Mask register */
54: static int nMwTransferSteps;
55:
56: static Uint32 nFrameStartAddr; /* Sound frame start */
57: static Uint32 nFrameEndAddr; /* Sound frame end */
58: static double FrameCounter; /* Counter in current sound frame */
59: static int nFrameLen; /* Length of the frame */
60:
1.1.1.3 root 61: static const double DmaSndSampleRates[4] =
1.1 root 62: {
63: 6258,
64: 12517,
65: 25033,
66: 50066
67: };
68:
69:
1.1.1.3 root 70: static const double DmaSndFalcSampleRates[] =
71: {
72: 49170,
73: 32780,
74: 24585,
75: 19668,
76: 16390,
77: 14049,
78: 12292,
79: 10927,
80: 9834,
81: 8940,
82: 8195,
83: 7565,
84: 7024,
85: 6556,
86: 6146,
87: };
88:
89:
1.1 root 90: /*-----------------------------------------------------------------------*/
1.1.1.3 root 91: /**
92: * Reset DMA sound variables.
93: */
1.1.1.4 root 94: void DmaSnd_Reset(bool bCold)
1.1 root 95: {
96: nDmaSoundControl = 0;
97:
98: if (bCold)
99: {
100: nDmaSoundMode = 0;
101: }
1.1.1.4 root 102:
103: nMwTransferSteps = 0;
1.1 root 104: }
105:
106:
107: /*-----------------------------------------------------------------------*/
1.1.1.3 root 108: /**
109: * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type)
110: */
1.1.1.4 root 111: void DmaSnd_MemorySnapShot_Capture(bool bSave)
1.1 root 112: {
113: /* Save/Restore details */
114: MemorySnapShot_Store(&nDmaSoundControl, sizeof(nDmaSoundControl));
115: MemorySnapShot_Store(&nDmaSoundMode, sizeof(nDmaSoundMode));
116: MemorySnapShot_Store(&nFrameStartAddr, sizeof(nFrameStartAddr));
117: MemorySnapShot_Store(&nFrameEndAddr, sizeof(nFrameEndAddr));
118: MemorySnapShot_Store(&FrameCounter, sizeof(FrameCounter));
119: MemorySnapShot_Store(&nFrameLen, sizeof(nFrameLen));
120: MemorySnapShot_Store(&nMicrowireData, sizeof(nMicrowireData));
121: MemorySnapShot_Store(&nMicrowireMask, sizeof(nMicrowireMask));
122: MemorySnapShot_Store(&nMwTransferSteps, sizeof(nMwTransferSteps));
123: }
124:
125:
1.1.1.3 root 126: static double DmaSnd_DetectSampleRate(void)
127: {
128: int nFalcClk = IoMem[0xff8935] & 0x0f;
129:
130: if (ConfigureParams.System.nMachineType == MACHINE_FALCON && nFalcClk != 0)
131: {
132: return DmaSndFalcSampleRates[nFalcClk-1];
133: }
134: else
135: {
136: return DmaSndSampleRates[nDmaSoundMode & 3];
137: }
138: }
139:
140:
1.1 root 141: /*-----------------------------------------------------------------------*/
1.1.1.3 root 142: /**
143: * This function is called when a new sound frame is started.
144: * It copies the start and end address from the I/O registers, calculates
145: * the sample length, etc.
146: */
1.1 root 147: static void DmaSnd_StartNewFrame(void)
148: {
149: int nCyclesForFrame;
1.1.1.4 root 150:
1.1 root 151: nFrameStartAddr = (IoMem[0xff8903] << 16) | (IoMem[0xff8905] << 8) | (IoMem[0xff8907] & ~1);
152: nFrameEndAddr = (IoMem[0xff890f] << 16) | (IoMem[0xff8911] << 8) | (IoMem[0xff8913] & ~1);
153:
154: FrameCounter = 0;
155: nFrameLen = nFrameEndAddr - nFrameStartAddr;
156:
157: /* To get smooth sound, set an "interrupt" for the end of the frame that
158: * updates the sound mix buffer. */
1.1.1.3 root 159: nCyclesForFrame = nFrameLen * (8013000.0 / DmaSnd_DetectSampleRate());
1.1 root 160: if (!(nDmaSoundMode & DMASNDMODE_MONO)) /* Is it stereo? */
161: nCyclesForFrame = nCyclesForFrame / 2;
1.1.1.4 root 162: Int_AddRelativeInterrupt(nCyclesForFrame, INT_CPU_CYCLE, INTERRUPT_DMASOUND);
1.1 root 163: }
164:
165:
166: /*-----------------------------------------------------------------------*/
1.1.1.3 root 167: /**
168: * Check if end-of-frame has been reached and raise interrupts if needed.
169: * Returns TRUE if DMA sound processing should be stopped now and FALSE
170: * if it continues.
171: */
1.1 root 172: static inline int DmaSnd_CheckForEndOfFrame(int nFrameCounter)
173: {
174: if (nFrameCounter >= nFrameLen)
175: {
176: /* Raise end-of-frame interrupts (MFP-i7 and Time-A) */
177: MFP_InputOnChannel(MFP_TIMER_GPIP7_BIT, MFP_IERA, &MFP_IPRA);
178: if (MFP_TACR == 0x08) /* Is timer A in Event Count mode? */
179: MFP_TimerA_EventCount_Interrupt();
180:
181: if (nDmaSoundControl & DMASNDCTRL_PLAYLOOP)
182: {
183: DmaSnd_StartNewFrame();
184: }
185: else
186: {
187: nDmaSoundControl &= ~DMASNDCTRL_PLAY;
188: return TRUE;
189: }
190: }
191:
192: return FALSE;
193: }
194:
195:
196: /*-----------------------------------------------------------------------*/
1.1.1.3 root 197: /**
198: * Mix DMA sound sample with the normal PSG sound samples.
199: */
1.1 root 200: void DmaSnd_GenerateSamples(int nMixBufIdx, int nSamplesToGenerate)
201: {
202: double FreqRatio;
203: int i;
204: int nBufIdx;
205: Sint8 *pFrameStart;
206:
207: if (!(nDmaSoundControl & DMASNDCTRL_PLAY))
208: return;
209:
210: pFrameStart = (Sint8 *)&STRam[nFrameStartAddr];
1.1.1.3 root 211: FreqRatio = DmaSnd_DetectSampleRate() / (double)SoundPlayBackFrequencies[OutputAudioFreqIndex];
1.1 root 212:
1.1.1.3 root 213: if (ConfigureParams.System.nMachineType == MACHINE_FALCON
214: && (nDmaSoundMode & DMASNDMODE_16BITSTEREO))
1.1 root 215: {
1.1.1.3 root 216: /* Stereo 16-bit */
217: FreqRatio *= 4.0;
218: for (i = 0; i < nSamplesToGenerate; i++)
219: {
220: nBufIdx = (nMixBufIdx + i) % MIXBUFFER_SIZE;
1.1.1.4 root 221: MixBuffer[nBufIdx][0] = ((int)MixBuffer[nBufIdx][0]
222: + (int)(*(Sint16*)&pFrameStart[((int)FrameCounter)&~1])) / 2;
223: MixBuffer[nBufIdx][1] = ((int)MixBuffer[nBufIdx][1]
224: + (int)(*(Sint16*)&pFrameStart[(((int)FrameCounter)&~1)+2])) / 2;
1.1.1.3 root 225: FrameCounter += FreqRatio;
226: if (DmaSnd_CheckForEndOfFrame(FrameCounter))
227: break;
228: }
229: }
230: else if (nDmaSoundMode & DMASNDMODE_MONO) /* 8-bit stereo or mono? */
231: {
232: /* Mono 8-bit */
1.1 root 233: for (i = 0; i < nSamplesToGenerate; i++)
234: {
235: nBufIdx = (nMixBufIdx + i) % MIXBUFFER_SIZE;
1.1.1.4 root 236: MixBuffer[nBufIdx][0] = MixBuffer[nBufIdx][1] =
237: ((int)MixBuffer[nBufIdx][0] + (((int)pFrameStart[(int)FrameCounter]) << 8)) / 2;
1.1 root 238: FrameCounter += FreqRatio;
239: if (DmaSnd_CheckForEndOfFrame(FrameCounter))
240: break;
241: }
242: }
243: else
244: {
1.1.1.3 root 245: /* Stereo 8-bit */
1.1 root 246: FreqRatio *= 2.0;
247: for (i = 0; i < nSamplesToGenerate; i++)
248: {
249: nBufIdx = (nMixBufIdx + i) % MIXBUFFER_SIZE;
1.1.1.4 root 250: MixBuffer[nBufIdx][0] = ((int)MixBuffer[nBufIdx][0]
251: + (((int)pFrameStart[((int)FrameCounter)&~1]) << 8)) / 2;
252: MixBuffer[nBufIdx][1] = ((int)MixBuffer[nBufIdx][1]
253: + (((int)pFrameStart[(((int)FrameCounter)&~1)+1]) << 8)) / 2;
1.1 root 254: FrameCounter += FreqRatio;
255: if (DmaSnd_CheckForEndOfFrame(FrameCounter))
256: break;
257: }
258: }
259: }
260:
261:
262: /*-----------------------------------------------------------------------*/
1.1.1.3 root 263: /**
264: * DMA sound end of frame "interrupt". Used for updating the sound after
265: * a frame has been finished.
266: */
1.1 root 267: void DmaSnd_InterruptHandler(void)
268: {
269: /* Remove this interrupt from list and re-order */
270: Int_AcknowledgeInterrupt();
271:
272: /* Update sound */
273: Sound_Update();
274: }
275:
276:
277: /*-----------------------------------------------------------------------*/
1.1.1.3 root 278: /**
279: * Create actual position for frame count registers.
280: */
1.1 root 281: static Uint32 DmaSnd_GetFrameCount(void)
282: {
283: Uint32 nActCount;
284:
285: if (nDmaSoundControl & DMASNDCTRL_PLAY)
286: nActCount = nFrameStartAddr + (int)FrameCounter;
287: else
288: nActCount = (IoMem[0xff8903] << 16) | (IoMem[0xff8905] << 8) | IoMem[0xff8907];
289:
290: nActCount &= ~1;
291:
292: return nActCount;
293: }
294:
295:
296: /*-----------------------------------------------------------------------*/
1.1.1.3 root 297: /**
298: * Read word from sound control register (0xff8900).
299: */
1.1 root 300: void DmaSnd_SoundControl_ReadWord(void)
301: {
302: IoMem_WriteWord(0xff8900, nDmaSoundControl);
303: }
304:
305:
306: /*-----------------------------------------------------------------------*/
1.1.1.3 root 307: /**
308: * Write word to sound control register (0xff8900).
1.1.1.4 root 309: *
310: * FIXME: add Falcon specific handler here...
1.1.1.3 root 311: */
1.1 root 312: void DmaSnd_SoundControl_WriteWord(void)
313: {
314: Uint16 nNewSndCtrl;
315:
316: nNewSndCtrl = IoMem_ReadWord(0xff8900) & 3;
317:
318: if (!(nDmaSoundControl & DMASNDCTRL_PLAY) && (nNewSndCtrl & DMASNDCTRL_PLAY))
319: {
320: //fprintf(stderr, "Turning on DMA sound emulation.\n");
321: DmaSnd_StartNewFrame();
322: }
323: else if ((nDmaSoundControl & DMASNDCTRL_PLAY) && !(nNewSndCtrl & DMASNDCTRL_PLAY))
324: {
325: //fprintf(stderr, "Turning off DMA sound emulation.\n");
326: }
327:
328: nDmaSoundControl = nNewSndCtrl;
329: }
330:
331:
332: /*-----------------------------------------------------------------------*/
1.1.1.3 root 333: /**
334: * Read word from sound frame count high register (0xff8909).
335: */
1.1 root 336: void DmaSnd_FrameCountHigh_ReadByte(void)
337: {
338: IoMem_WriteByte(0xff8909, DmaSnd_GetFrameCount() >> 16);
339: }
340:
341:
342: /*-----------------------------------------------------------------------*/
1.1.1.3 root 343: /**
344: * Read word from sound frame count medium register (0xff890b).
345: */
1.1 root 346: void DmaSnd_FrameCountMed_ReadByte(void)
347: {
348: IoMem_WriteByte(0xff890b, DmaSnd_GetFrameCount() >> 8);
349: }
350:
351:
352: /*-----------------------------------------------------------------------*/
1.1.1.3 root 353: /**
354: * Read word from sound frame count low register (0xff890d).
355: */
1.1 root 356: void DmaSnd_FrameCountLow_ReadByte(void)
357: {
358: IoMem_WriteByte(0xff890d, DmaSnd_GetFrameCount());
359: }
360:
361:
362: /*-----------------------------------------------------------------------*/
1.1.1.3 root 363: /**
364: * Read word from sound mode register (0xff8920).
365: */
1.1 root 366: void DmaSnd_SoundMode_ReadWord(void)
367: {
368: IoMem_WriteWord(0xff8920, nDmaSoundMode);
369: }
370:
371:
372: /*-----------------------------------------------------------------------*/
1.1.1.3 root 373: /**
374: * Write word to sound mode register (0xff8920).
375: * Handling framework for Falcon specific bits by Matthias Arndt <[email protected]>
376: */
1.1 root 377: void DmaSnd_SoundMode_WriteWord(void)
378: {
1.1.1.3 root 379: /* Handle Falcon specialities: STE and TT only have bits 7,1 and 0 in this register */
380:
381: /* Falcon has meaning in almost all bits of SND_SMC */
382: if (ConfigureParams.System.nMachineType == MACHINE_FALCON)
383: {
1.1.1.4 root 384:
1.1.1.3 root 385: nDmaSoundMode = IoMem_ReadWord(0xff8920);
386: /* FIXME: add code here to evaluate Falcon specific settings */
1.1.1.4 root 387:
388:
1.1.1.3 root 389: } else {
390: /* STE or TT - hopefully STFM emulation never gets here :)
391: * we maskout the Falcon only bits so we only hit bits that exist on a real STE
392: */
393: nDmaSoundMode = (IoMem_ReadWord(0xff8920)&0x008F);
394: /* we also write the masked value back into the emulated hw registers so we have a correct value there */
395: IoMem_WriteWord(0xff8920,nDmaSoundMode);
396: }
1.1 root 397: //fprintf(stderr,"New sound mode = $%x\n", nDmaSoundMode);
398: }
399:
400:
401: /*-----------------------------------------------------------------------*/
1.1.1.3 root 402: /**
1.1.1.4 root 403: * Handle the shifting/rotating of the microwire registers
404: * The microwire regs should be done after 16 usec = 32 NOPs = 128 cycles.
405: * That means we have to shift 16 times with a delay of 8 cycles.
1.1.1.3 root 406: */
1.1.1.4 root 407: void DmaSnd_InterruptHandler_Microwire(void)
1.1 root 408: {
1.1.1.4 root 409: /* Remove this interrupt from list and re-order */
410: Int_AcknowledgeInterrupt();
411:
412: --nMwTransferSteps;
413:
414: /* Shift data register until it becomes zero. */
415: if (nMwTransferSteps > 1)
1.1 root 416: {
1.1.1.4 root 417: IoMem_WriteWord(0xff8922, nMicrowireData<<(16-nMwTransferSteps));
1.1 root 418: }
419: else
420: {
1.1.1.4 root 421: /* Paradox XMAS 2004 demo continuesly writes to the data
422: * register, but still expects to read a zero inbetween,
423: * so we have to output a zero before we're really done
424: * with the transfer. */
1.1 root 425: IoMem_WriteWord(0xff8922, 0);
426: }
1.1.1.4 root 427:
428: /* Rotate mask register */
429: IoMem_WriteWord(0xff8924, (nMicrowireMask<<(16-nMwTransferSteps))
430: |(nMicrowireMask>>nMwTransferSteps));
431:
432: if (nMwTransferSteps > 0)
433: {
434: Int_AddRelativeInterrupt(8, INT_CPU_CYCLE, INTERRUPT_DMASOUND_MICROWIRE);
435: }
436: }
437:
438:
439: /**
440: * Read word from microwire data register (0xff8922).
441: */
442: void DmaSnd_MicrowireData_ReadWord(void)
443: {
444: /* Shifting is done in DmaSnd_InterruptHandler_Microwire! */
445: //fprintf(stderr, "MW data read: 0x%x\n", IoMem_ReadWord(0xff8922));
1.1 root 446: }
447:
448:
1.1.1.3 root 449: /**
450: * Write word to microwire data register (0xff8922).
451: */
1.1 root 452: void DmaSnd_MicrowireData_WriteWord(void)
453: {
1.1.1.4 root 454: /* Only update, if no shift is in progress */
455: if (!nMwTransferSteps)
456: {
457: nMicrowireData = IoMem_ReadWord(0xff8922);
458: /* Start shifting events to simulate a microwire transfer */
459: nMwTransferSteps = 16;
460: Int_AddRelativeInterrupt(8, INT_CPU_CYCLE, INTERRUPT_DMASOUND_MICROWIRE);
461: }
462:
463: //fprintf(stderr, "MW data write: 0x%x\n", IoMem_ReadWord(0xff8922));
1.1 root 464: }
465:
466:
1.1.1.3 root 467: /**
468: * Read word from microwire mask register (0xff8924).
469: */
1.1 root 470: void DmaSnd_MicrowireMask_ReadWord(void)
471: {
1.1.1.4 root 472: /* Same as with data register, but mask is rotated, not shifted. */
473: //fprintf(stderr, "MW mask read: 0x%x\n", IoMem_ReadWord(0xff8924));
1.1 root 474: }
475:
476:
1.1.1.3 root 477: /**
478: * Write word to microwire mask register (0xff8924).
479: */
1.1 root 480: void DmaSnd_MicrowireMask_WriteWord(void)
481: {
1.1.1.4 root 482: /* Only update, if no shift is in progress */
483: if (!nMwTransferSteps)
484: {
485: nMicrowireMask = IoMem_ReadWord(0xff8924);
486: }
487:
488: //fprintf(stderr, "MW mask write: 0x%x\n", IoMem_ReadWord(0xff8924));
1.1 root 489: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.