1 // Megadrive ym2612 interface for eke-eke's genplus-gx ym2612, based on MAME's ym2612
2 // Note: if a mono route is needed (in the future), uncomment in the main render loop.
3 
4 #include "burnint.h"
5 #include "burn_md2612.h"
6 
7 void (*BurnMD2612Update)(INT16* pSoundBuf, INT32 nSegmentEnd);
8 
9 static INT32 (*BurnMD2612StreamCallback)(INT32 nSoundRate);
10 
11 static INT32 nBurnMD2612SoundRate;
12 
13 static INT16* pBuffer;
14 static INT16* pMD2612Buffer[2];
15 
16 static INT32 nMD2612Position;
17 
18 static UINT32 nSampleSize;
19 static INT32 nFractionalPosition;
20 
21 static INT32 nNumChips = 0;
22 static INT32 bMD2612AddSignal;
23 
24 static double MD2612Volumes[2];
25 static INT32 MD2612RouteDirs[2];
26 
27 // ----------------------------------------------------------------------------
28 // Dummy functions
29 
MD2612UpdateDummy(INT16 *,INT32)30 static void MD2612UpdateDummy(INT16*, INT32)
31 {
32 	return;
33 }
34 
MD2612StreamCallbackDummy(INT32)35 static INT32 MD2612StreamCallbackDummy(INT32)
36 {
37 	return 0;
38 }
39 
40 // ----------------------------------------------------------------------------
41 // Execute MD2612 for part of a frame
42 
MD2612Render(INT32 nSegmentLength)43 static void MD2612Render(INT32 nSegmentLength)
44 {
45 #if defined FBA_DEBUG
46 	if (!DebugSnd_YM2612Initted) bprintf(PRINT_ERROR, _T("MD2612Render called without init\n"));
47 #endif
48 
49 	if (nMD2612Position >= nSegmentLength) {
50 		return;
51 	}
52 
53 	nSegmentLength -= nMD2612Position;
54 
55 	pMD2612Buffer[0] = pBuffer + 0 * 4096 + 4 + nMD2612Position;
56 	pMD2612Buffer[1] = pBuffer + 1 * 4096 + 4 + nMD2612Position;
57 
58 	MDYM2612Update(&pMD2612Buffer[0], nSegmentLength);
59 
60 	nMD2612Position += nSegmentLength;
61 }
62 
63 // ----------------------------------------------------------------------------
64 
65 // Update the sound buffer
66 
67 #define INTERPOLATE_ADD_SOUND_LEFT(route, buffer)																	\
68 	if ((MD2612RouteDirs[route] & BURN_SND_ROUTE_LEFT) == BURN_SND_ROUTE_LEFT) {									\
69 		nLeftSample[0] += (INT32)(pMD2612Buffer[buffer][(nFractionalPosition >> 16) - 3]/* * MD2612Volumes[route]*/);	\
70 		nLeftSample[1] += (INT32)(pMD2612Buffer[buffer][(nFractionalPosition >> 16) - 2]/* * MD2612Volumes[route]*/);	\
71 		nLeftSample[2] += (INT32)(pMD2612Buffer[buffer][(nFractionalPosition >> 16) - 1]/* * MD2612Volumes[route]*/);	\
72 		nLeftSample[3] += (INT32)(pMD2612Buffer[buffer][(nFractionalPosition >> 16) - 0]/* * MD2612Volumes[route]*/);	\
73 	}
74 
75 #define INTERPOLATE_ADD_SOUND_RIGHT(route, buffer)																	\
76 	if ((MD2612RouteDirs[route] & BURN_SND_ROUTE_RIGHT) == BURN_SND_ROUTE_RIGHT) {									\
77 		nRightSample[0] += (INT32)(pMD2612Buffer[buffer][(nFractionalPosition >> 16) - 3]/* * MD2612Volumes[route]*/);	\
78 		nRightSample[1] += (INT32)(pMD2612Buffer[buffer][(nFractionalPosition >> 16) - 2]/* * MD2612Volumes[route]*/);	\
79 		nRightSample[2] += (INT32)(pMD2612Buffer[buffer][(nFractionalPosition >> 16) - 1]/* * MD2612Volumes[route]*/);	\
80 		nRightSample[3] += (INT32)(pMD2612Buffer[buffer][(nFractionalPosition >> 16) - 0]/* * MD2612Volumes[route]*/);	\
81 	}
82 
MD2612UpdateResample(INT16 * pSoundBuf,INT32 nSegmentEnd)83 static void MD2612UpdateResample(INT16* pSoundBuf, INT32 nSegmentEnd)
84 {
85 #if defined FBA_DEBUG
86 	if (!DebugSnd_YM2612Initted) bprintf(PRINT_ERROR, _T("MD2612UpdateResample called without init\n"));
87 #endif
88 
89 	INT32 nSegmentLength = nSegmentEnd;
90 	INT32 nSamplesNeeded = nSegmentEnd * nBurnMD2612SoundRate / nBurnSoundRate + 1;
91 
92 	if (nSamplesNeeded < nMD2612Position) {
93 		nSamplesNeeded = nMD2612Position;
94 	}
95 
96 	if (nSegmentLength > nBurnSoundLen) {
97 		nSegmentLength = nBurnSoundLen;
98 	}
99 	nSegmentLength <<= 1;
100 
101 	MD2612Render(nSamplesNeeded);
102 
103 	pMD2612Buffer[0] = pBuffer + 0 * 4096 + 4;
104 	pMD2612Buffer[1] = pBuffer + 1 * 4096 + 4;
105 
106 	for (INT32 i = (nFractionalPosition & 0xFFFF0000) >> 15; i < nSegmentLength; i += 2, nFractionalPosition += nSampleSize) {
107 		INT32 nLeftSample[4] = {0, 0, 0, 0};
108 		INT32 nRightSample[4] = {0, 0, 0, 0};
109 		INT32 nTotalLeftSample, nTotalRightSample;
110 
111 		INTERPOLATE_ADD_SOUND_LEFT  (BURN_SND_MD2612_MD2612_ROUTE_1, 0)
112 		//INTERPOLATE_ADD_SOUND_RIGHT (BURN_SND_MD2612_MD2612_ROUTE_1, 0) // uncomment these to fix MONO, if needed in the future. (unlikely)
113 		//INTERPOLATE_ADD_SOUND_LEFT  (BURN_SND_MD2612_MD2612_ROUTE_2, 1)
114 		INTERPOLATE_ADD_SOUND_RIGHT (BURN_SND_MD2612_MD2612_ROUTE_2, 1)
115 
116 		nTotalLeftSample  = INTERPOLATE4PS_16BIT((nFractionalPosition >> 4) & 0x0fff, nLeftSample[0], nLeftSample[1], nLeftSample[2], nLeftSample[3]);
117 		nTotalRightSample = INTERPOLATE4PS_16BIT((nFractionalPosition >> 4) & 0x0fff, nRightSample[0], nRightSample[1], nRightSample[2], nRightSample[3]);
118 
119 		nTotalLeftSample  = BURN_SND_CLIP(nTotalLeftSample * MD2612Volumes[BURN_SND_MD2612_MD2612_ROUTE_1]);
120 		nTotalRightSample = BURN_SND_CLIP(nTotalRightSample * MD2612Volumes[BURN_SND_MD2612_MD2612_ROUTE_2]);
121 
122 		if (bMD2612AddSignal) {
123 			pSoundBuf[i + 0] = BURN_SND_CLIP(pSoundBuf[i + 0] + nTotalLeftSample);
124 			pSoundBuf[i + 1] = BURN_SND_CLIP(pSoundBuf[i + 1] + nTotalRightSample);
125 		} else {
126 			pSoundBuf[i + 0] = nTotalLeftSample;
127 			pSoundBuf[i + 1] = nTotalRightSample;
128 		}
129 	}
130 
131 	if (nSegmentEnd >= nBurnSoundLen) {
132 		INT32 nExtraSamples = nSamplesNeeded - (nFractionalPosition >> 16);
133 
134 		for (INT32 i = -4; i < nExtraSamples; i++) {
135 			pMD2612Buffer[0][i] = pMD2612Buffer[0][(nFractionalPosition >> 16) + i];
136 			pMD2612Buffer[1][i] = pMD2612Buffer[1][(nFractionalPosition >> 16) + i];
137 		}
138 
139 		nFractionalPosition &= 0xFFFF;
140 
141 		nMD2612Position = nExtraSamples;
142 	}
143 }
144 
145 // ----------------------------------------------------------------------------
146 // Callbacks for YM2612 core
BurnMD2612UpdateRequest()147 void BurnMD2612UpdateRequest()
148 {
149 #if defined FBA_DEBUG
150 	if (!DebugSnd_YM2612Initted) bprintf(PRINT_ERROR, _T("YM2612UpdateRequest called without init\n"));
151 #endif
152 
153 	MD2612Render(BurnMD2612StreamCallback(nBurnMD2612SoundRate));
154 }
155 
156 // ----------------------------------------------------------------------------
157 // Initialisation, etc.
158 
BurnMD2612Reset()159 void BurnMD2612Reset()
160 {
161 #if defined FBA_DEBUG
162 	if (!DebugSnd_YM2612Initted) bprintf(PRINT_ERROR, _T("BurnMD2612Reset called without init\n"));
163 #endif
164 
165 	MDYM2612Reset();
166 }
167 
BurnMD2612Exit()168 void BurnMD2612Exit()
169 {
170 #if defined FBA_DEBUG
171 	if (!DebugSnd_YM2612Initted) bprintf(PRINT_ERROR, _T("BurnMD2612Exit called without init\n"));
172 #endif
173 
174 	if (!DebugSnd_YM2612Initted) return;
175 
176 	MDYM2612Exit();
177 
178 	BurnFree(pBuffer);
179 
180 	nNumChips = 0;
181 	bMD2612AddSignal = 0;
182 
183 	DebugSnd_YM2612Initted = 0;
184 }
185 
BurnMD2612Init(INT32 num,INT32 bIsPal,INT32 (* StreamCallback)(INT32),INT32 bAddSignal)186 INT32 BurnMD2612Init(INT32 num, INT32 bIsPal, INT32 (*StreamCallback)(INT32), INT32 bAddSignal)
187 {
188 	if (num > 1) {
189 		bprintf(0, _T("BurnMD2612Init(): MD2612 only supports 1 chip!\n"));
190 		return 0;
191 	}
192 
193 	DebugSnd_YM2612Initted = 1;
194 
195 	if (nBurnSoundRate <= 0) {
196 		BurnMD2612StreamCallback = MD2612StreamCallbackDummy;
197 
198 		BurnMD2612Update = MD2612UpdateDummy;
199 
200 		MDYM2612Init();
201 		return 0;
202 	}
203 
204 	BurnMD2612StreamCallback = StreamCallback;
205 
206 	if (!StreamCallback) {
207 		bprintf(0, _T("BurnMD2612Init(): StreamCallback is NULL! Crashing in 3..2...1....\n"));
208 	}
209 
210 	// Megadrive's 2612 runs at 53267hz NTSC, 52781hz PAL
211 	nBurnMD2612SoundRate = (bIsPal) ? 52781 : 53267;
212 	BurnMD2612Update = MD2612UpdateResample;
213 	nSampleSize = (UINT32)nBurnMD2612SoundRate * (1 << 16) / nBurnSoundRate;
214 
215 	MDYM2612Init();
216 
217 	pBuffer = (INT16*)BurnMalloc(4096 * 2 * num * sizeof(INT16));
218 	memset(pBuffer, 0, 4096 * 2 * num * sizeof(INT16));
219 
220 	nMD2612Position = 0;
221 	nFractionalPosition = 0;
222 
223 	nNumChips = num;
224 	bMD2612AddSignal = bAddSignal;
225 
226 	// default routes
227 	MD2612Volumes[BURN_SND_MD2612_MD2612_ROUTE_1] = 1.00;
228 	MD2612Volumes[BURN_SND_MD2612_MD2612_ROUTE_2] = 1.00;
229 	MD2612RouteDirs[BURN_SND_MD2612_MD2612_ROUTE_1] = BURN_SND_ROUTE_LEFT;
230 	MD2612RouteDirs[BURN_SND_MD2612_MD2612_ROUTE_2] = BURN_SND_ROUTE_RIGHT;
231 
232 	return 0;
233 }
234 
BurnMD2612SetRoute(INT32 nChip,INT32 nIndex,double nVolume,INT32 nRouteDir)235 void BurnMD2612SetRoute(INT32 nChip, INT32 nIndex, double nVolume, INT32 nRouteDir)
236 {
237 #if defined FBA_DEBUG
238 	if (!DebugSnd_YM2612Initted) bprintf(PRINT_ERROR, _T("BurnMD2612SetRoute called without init\n"));
239 	if (nIndex < 0 || nIndex > 1) bprintf(PRINT_ERROR, _T("BurnMD2612SetRoute called with invalid index %i\n"), nIndex);
240 	if (nChip >= nNumChips) bprintf(PRINT_ERROR, _T("BurnMD2612SetRoute called with invalid chip %i\n"), nChip);
241 #endif
242 
243 	if (nChip == 0) {
244 		MD2612Volumes[nIndex] = nVolume;
245 		MD2612RouteDirs[nIndex] = nRouteDir;
246 	}
247 }
248 
BurnMD2612Scan(INT32 nAction,INT32 * pnMin)249 void BurnMD2612Scan(INT32 nAction, INT32* pnMin)
250 {
251 #if defined FBA_DEBUG
252 	if (!DebugSnd_YM2612Initted) bprintf(PRINT_ERROR, _T("BurnMD2612Scan called without init\n"));
253 #endif
254 
255 	if (nAction & ACB_DRIVER_DATA) {
256 		SCAN_VAR(nMD2612Position);
257 
258 		if (nAction & ACB_WRITE) {
259 			MDYM2612LoadContext();
260 
261 			nMD2612Position = 0;
262 			nFractionalPosition = 0;
263 			memset(pBuffer, 0, 4096 * 2 * 1 * sizeof(INT16));
264 		} else {
265 			MDYM2612SaveContext();
266 		}
267 	}
268 }
269