forked from mmitch/gbsplay
-
Notifications
You must be signed in to change notification settings - Fork 0
/
plugout_vgm.c
161 lines (132 loc) · 3.97 KB
/
plugout_vgm.c
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
/*
* gbsplay is a Gameboy sound player
*
* VGM output plugin
* 2022 (C) by Maximilian Rehkopf <otakon@gmx.net>
*
* Licensed under GNU GPL v1 or, at your option, any later version.
*/
#include "common.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "filewriter.h"
#include "plugout.h"
#include "util.h"
#define VGM_FILE_VERSION (0x161)
#define VGM_DMG_CLOCK (0x400000)
#define VGM_WAITSAMPLES_MAX (0xffff)
#define VGM_TICKS_PER_SECOND (44100)
#define VGM_OFS_NUMSAMPLES (0x18)
#define VGM_OFS_DATA_START (0x34)
#define VGM_OFS_DMG_CLOCK (0x80)
#define VGM_HDR_LEN (0x84)
#define VGM_CMD_WAITSAMPLES (0x61)
#define VGM_CMD_WAITSAMPLES_SHORT (0x70)
#define VGM_CMD_END (0x66)
#define VGM_CMD_DMGWRITE (0xb3)
#define VGM_DATA_START_REL (VGM_HDR_LEN - VGM_OFS_DATA_START)
static FILE *vgmfile;
static double samples_total = 0;
static double samples_prev = 0;
static double sample_diff_acc = 0;
static const uint8_t blank_hdr[VGM_HDR_LEN];
/* finalize VGM output file */
static void vgm_finalize(void) {
size_t eof_offset;
fpack(vgmfile, "<bwb",
/* append a second of delay at the end (for sounds to finish) */
VGM_CMD_WAITSAMPLES, VGM_TICKS_PER_SECOND,
/* write end of data marker */
VGM_CMD_END
);
/* fill in header entries */
eof_offset = ftell(vgmfile) - 4;
fpackat(vgmfile, 0, "<{Vgm }dd", eof_offset, VGM_FILE_VERSION);
fpackat(vgmfile, VGM_OFS_DMG_CLOCK, "<d", VGM_DMG_CLOCK);
fpackat(vgmfile, VGM_OFS_DATA_START, "<d", VGM_DATA_START_REL);
fpackat(vgmfile, VGM_OFS_NUMSAMPLES, "<d", (uint32_t)samples_total);
}
static long vgm_open(enum plugout_endian *endian, long rate, long *buffer_bytes, const struct plugout_metadata metadata)
{
UNUSED(endian);
UNUSED(rate);
UNUSED(buffer_bytes);
UNUSED(metadata);
return 0;
}
static int vgm_open_file(int subsong) {
vgmfile = file_open("vgm", subsong);
if (vgmfile == NULL) {
fprintf(stderr, "Can't open output file: %s\n", strerror(errno));
return -1;
}
/* zero-pad header area */
fwrite(blank_hdr, sizeof(blank_hdr), 1, vgmfile);
sample_diff_acc = 0;
samples_prev = 0;
return 0;
}
static int vgm_close_file(void) {
int result;
vgm_finalize();
result = fclose(vgmfile);
vgmfile = NULL;
return result;
}
static int vgm_skip(int subsong) {
if (vgmfile) {
if (vgm_close_file()) {
return 1;
};
}
return vgm_open_file(subsong);
}
static int vgm_io(cycles_t cycles, uint32_t addr, uint8_t val) {
double sample_diff;
int vgm_sample_diff;
uint8_t vgmreg;
/* calculate fractional samples (VGM counts everything as 44100Hz samples) */
samples_total = (double)cycles * (double)VGM_TICKS_PER_SECOND / (double)VGM_DMG_CLOCK;
sample_diff = samples_total - samples_prev;
/* accumulate fractional samples, use integer part as delay time in samples */
sample_diff_acc += sample_diff;
vgm_sample_diff = sample_diff_acc;
/* subtract integer part, keeping fractional part for further accumulation */
sample_diff_acc -= vgm_sample_diff;
/* write calculated sample delay commands in chunks of <= 65535 samples.
use single-byte delay shortcut command on 1..16 sample delays. */
while (vgm_sample_diff > 0) {
if (vgm_sample_diff < 17) {
fputc(VGM_CMD_WAITSAMPLES_SHORT | (vgm_sample_diff - 1), vgmfile);
} else {
fpack(vgmfile, "<bw",
VGM_CMD_WAITSAMPLES,
(vgm_sample_diff > VGM_WAITSAMPLES_MAX)
? VGM_WAITSAMPLES_MAX
: vgm_sample_diff);
}
vgm_sample_diff -= VGM_WAITSAMPLES_MAX;
}
/* dump the register write if register is valid for VGM dump. */
if (addr >= 0xff10) {
vgmreg = addr - 0xff10;
fpack(vgmfile, "<bbb", VGM_CMD_DMGWRITE, vgmreg, val);
}
samples_prev = samples_total;
return 0;
}
static void vgm_close(void) {
if (vgmfile) {
vgm_close_file();
}
}
const struct output_plugin plugout_vgm = {
.name = "vgm",
.description = "VGM file writer",
.open = vgm_open,
.skip = vgm_skip,
.io = vgm_io,
.close = vgm_close
};