Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
Path: blob/a-new-beginning/Cherry/Core/VgmRecorder.cpp
2 views
1
/*
2
* Gearcoleco - ColecoVision Emulator
3
* Copyright (C) 2021 Ignacio Sanchez
4
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation, either version 3 of the License, or
8
* any later version.
9
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
14
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see http://www.gnu.org/licenses/
17
*
18
*/
19
20
#include "VgmRecorder.h"
21
#include "log.h"
22
#include <cstring>
23
24
VgmRecorder::VgmRecorder()
25
{
26
m_bRecording = false;
27
m_PendingWait = 0;
28
m_TotalSamples = 0;
29
m_ClockRate = 0;
30
m_bPAL = false;
31
m_bPSGUsed = false;
32
m_bAY8910Used = false;
33
}
34
35
VgmRecorder::~VgmRecorder()
36
{
37
if (m_bRecording)
38
{
39
Stop();
40
}
41
}
42
43
void VgmRecorder::Start(const char* file_path, int clock_rate, bool is_pal)
44
{
45
if (m_bRecording)
46
return;
47
48
m_FilePath = file_path;
49
m_ClockRate = clock_rate;
50
m_bPAL = is_pal;
51
m_bRecording = true;
52
m_PendingWait = 0;
53
m_TotalSamples = 0;
54
m_bPSGUsed = false;
55
m_bAY8910Used = false;
56
m_CommandBuffer.clear();
57
}
58
59
void VgmRecorder::Stop()
60
{
61
if (!m_bRecording)
62
return;
63
64
FlushPendingWait();
65
66
// Write end of sound data command
67
WriteCommand(0x66);
68
69
std::ofstream file(m_FilePath.c_str(), std::ios::binary);
70
if (file.is_open())
71
{
72
u8 header[256];
73
memset(header, 0, 256);
74
75
// File identification "Vgm " (0x56 0x67 0x6d 0x20)
76
header[0x00] = 0x56;
77
header[0x01] = 0x67;
78
header[0x02] = 0x6d;
79
header[0x03] = 0x20;
80
81
// EOF offset (file length - 4)
82
u32 eof_offset = (256 + (u32)m_CommandBuffer.size()) - 4;
83
header[0x04] = (eof_offset >> 0) & 0xFF;
84
header[0x05] = (eof_offset >> 8) & 0xFF;
85
header[0x06] = (eof_offset >> 16) & 0xFF;
86
header[0x07] = (eof_offset >> 24) & 0xFF;
87
88
// Version number (1.70 = 0x00000170)
89
header[0x08] = 0x70;
90
header[0x09] = 0x01;
91
header[0x0A] = 0x00;
92
header[0x0B] = 0x00;
93
94
// SN76489 clock
95
if (m_bPSGUsed)
96
{
97
u32 psg_clock = m_ClockRate;
98
header[0x0C] = (psg_clock >> 0) & 0xFF;
99
header[0x0D] = (psg_clock >> 8) & 0xFF;
100
header[0x0E] = (psg_clock >> 16) & 0xFF;
101
header[0x0F] = (psg_clock >> 24) & 0xFF;
102
}
103
104
// YM2413 clock (not used, set to 0)
105
header[0x10] = 0x00;
106
header[0x11] = 0x00;
107
header[0x12] = 0x00;
108
header[0x13] = 0x00;
109
110
// GD3 offset (0 = no GD3 tag)
111
header[0x14] = 0x00;
112
header[0x15] = 0x00;
113
header[0x16] = 0x00;
114
header[0x17] = 0x00;
115
116
// Total # samples
117
header[0x18] = (m_TotalSamples >> 0) & 0xFF;
118
header[0x19] = (m_TotalSamples >> 8) & 0xFF;
119
header[0x1A] = (m_TotalSamples >> 16) & 0xFF;
120
header[0x1B] = (m_TotalSamples >> 24) & 0xFF;
121
122
// Loop offset (0 = no loop)
123
header[0x1C] = 0x00;
124
header[0x1D] = 0x00;
125
header[0x1E] = 0x00;
126
header[0x1F] = 0x00;
127
128
// Loop # samples (0 = no loop)
129
header[0x20] = 0x00;
130
header[0x21] = 0x00;
131
header[0x22] = 0x00;
132
header[0x23] = 0x00;
133
134
u32 rate = m_bPAL ? 50 : 60;
135
header[0x24] = (rate >> 0) & 0xFF;
136
header[0x25] = (rate >> 8) & 0xFF;
137
header[0x26] = (rate >> 16) & 0xFF;
138
header[0x27] = (rate >> 24) & 0xFF;
139
140
// SN76489 feedback and flags
141
if (m_bPSGUsed)
142
{
143
// SN76489 feedback (0x0009 for SMS/GG/ColecoVision)
144
header[0x28] = 0x09;
145
header[0x29] = 0x00;
146
147
// SN76489 shift register width (16 for SMS/GG/ColecoVision)
148
header[0x2A] = 0x10;
149
150
// SN76489 Flags (bit 2 = GameGear stereo)
151
header[0x2B] = 0x00;
152
}
153
154
// VGM data offset (relative from 0x34)
155
// Data starts at 0x100 (256 bytes), so offset from 0x34 is 0x100 - 0x34 = 0xCC
156
header[0x34] = 0xCC;
157
header[0x35] = 0x00;
158
header[0x36] = 0x00;
159
header[0x37] = 0x00;
160
161
// AY8910 clock
162
if (m_bAY8910Used)
163
{
164
u32 ay_clock = m_ClockRate;
165
header[0x74] = (ay_clock >> 0) & 0xFF;
166
header[0x75] = (ay_clock >> 8) & 0xFF;
167
header[0x76] = (ay_clock >> 16) & 0xFF;
168
header[0x77] = (ay_clock >> 24) & 0xFF;
169
170
// AY8910 Chip Type (0x00 = AY-3-8910)
171
header[0x78] = 0x00;
172
173
// AY8910 Flags (0x01 = Legacy Output)
174
header[0x79] = 0x01;
175
}
176
177
// Write header
178
file.write(reinterpret_cast<const char*>(header), 256);
179
180
// Write command buffer
181
file.write(reinterpret_cast<const char*>(&m_CommandBuffer[0]), m_CommandBuffer.size());
182
183
file.close();
184
}
185
186
m_bRecording = false;
187
m_CommandBuffer.clear();
188
}
189
190
void VgmRecorder::WritePSG(u8 data)
191
{
192
if (!m_bRecording)
193
return;
194
195
FlushPendingWait();
196
197
// Mark PSG as used
198
m_bPSGUsed = true;
199
200
// 0x50 dd - PSG (SN76489/SN76496) write value dd
201
WriteCommand(0x50, data);
202
}
203
204
void VgmRecorder::WriteAY8910(u8 reg, u8 data)
205
{
206
if (!m_bRecording)
207
return;
208
209
FlushPendingWait();
210
211
// Mark AY8910 as used
212
m_bAY8910Used = true;
213
214
// 0xA0 aa dd - AY8910, write value dd to register aa
215
WriteCommand(0xA0, reg, data);
216
}
217
218
void VgmRecorder::UpdateTiming(int elapsed_samples)
219
{
220
if (!m_bRecording)
221
return;
222
223
m_PendingWait += elapsed_samples;
224
m_TotalSamples += elapsed_samples;
225
}
226
227
void VgmRecorder::WriteCommand(u8 command)
228
{
229
m_CommandBuffer.push_back(command);
230
}
231
232
void VgmRecorder::WriteCommand(u8 command, u8 data)
233
{
234
m_CommandBuffer.push_back(command);
235
m_CommandBuffer.push_back(data);
236
}
237
238
void VgmRecorder::WriteCommand(u8 command, u8 data1, u8 data2)
239
{
240
m_CommandBuffer.push_back(command);
241
m_CommandBuffer.push_back(data1);
242
m_CommandBuffer.push_back(data2);
243
}
244
245
void VgmRecorder::WriteWait(int samples)
246
{
247
if (samples <= 0)
248
return;
249
250
while (samples > 0)
251
{
252
if (samples == 735)
253
{
254
// 0x62 - wait 735 samples (60th of a second)
255
WriteCommand(0x62);
256
samples -= 735;
257
}
258
else if (samples == 882)
259
{
260
// 0x63 - wait 882 samples (50th of a second)
261
WriteCommand(0x63);
262
samples -= 882;
263
}
264
else if (samples <= 16)
265
{
266
// 0x7n - wait n+1 samples, n can range from 0 to 15
267
WriteCommand(0x70 + (samples - 1));
268
samples = 0;
269
}
270
else if (samples <= 65535)
271
{
272
// 0x61 nn nn - Wait n samples
273
WriteCommand(0x61);
274
m_CommandBuffer.push_back(samples & 0xFF);
275
m_CommandBuffer.push_back((samples >> 8) & 0xFF);
276
samples = 0;
277
}
278
else
279
{
280
// Write maximum wait and continue
281
WriteCommand(0x61);
282
m_CommandBuffer.push_back(0xFF);
283
m_CommandBuffer.push_back(0xFF);
284
samples -= 65535;
285
}
286
}
287
}
288
289
void VgmRecorder::FlushPendingWait()
290
{
291
if (m_PendingWait > 0)
292
{
293
WriteWait(m_PendingWait);
294
m_PendingWait = 0;
295
}
296
}
297
298