-
Notifications
You must be signed in to change notification settings - Fork 2
/
imfplay.c
173 lines (153 loc) · 3.15 KB
/
imfplay.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
162
163
164
165
166
167
168
169
170
171
172
#include "ass.h"
/*
TECHNICAL NOTE
--------------
IMF files normally run at one of three speeds:
1. 280 Hz, in Duke Nukem 2.
2. 560 Hz, in most other games that use the format.
3. 700 Hz, in the 3D games.
Because the A3X *cannot* as it is now run this thing at any of these speeds,
we can either run it several times per VBlank (16 times to be precise) or
re-time the song data to similar effect.
I have chosen death. So to prepare a given IMF file for use on the A3X, use
the IMFCRUSH utility by K1n9_Duk3 as follows:
imfcrush <infile> <outfile> /convert 560 30
assuming it's one of the 2D games and any DN2 songs have already been adjusted
for playback with modern tools.
*/
#define BYTESWAP(x) x = ((x & 0xFF00) >> 8) | ((x & 0xFF) << 8);
static const uint16_t *imfdata;
static const uint16_t *_imfptr;
static uint16_t _imfwait, _imfsize;
static bool imfLoop = true;
void(*nextVBlank)(void);
typedef struct
{
uint16_t length;
uint16_t notlengthlol;
uint16_t priority;
uint8_t settings[16];
uint8_t octave;
uint8_t pitches[];
} audiot;
static audiot *_audiot;
static uint16_t _audiotptr, _audiotlen, _audiotpriority;
static uint8_t _audiotblock;
static bool _audiotnote;
static void IMF_Service()
{
while ((_imfsize) && (!_imfwait))
{
REG_OPLOUT = *_imfptr++;
_imfwait = *_imfptr++;
_imfwait /= 128;
_imfsize -= 4;
}
_imfwait--;
if (!_imfsize)
{
if (imfLoop)
{
_imfptr = imfdata;
_imfsize = (*_imfptr++) & 0xFCFF;
BYTESWAP(_imfsize);
} else
{
_imfptr = NULL;
}
_imfwait = 0;
}
}
static void IMF_Play()
{
if (_audiotlen)
{
for (int steps = 0; steps < 2; steps++)
{
uint8_t pit = _audiot->pitches[_audiotptr];
if (pit == 0x00)
{
REG_OPLOUT = 0xB000 | _audiotblock;
_audiotnote = false;
}
else
{
REG_OPLOUT = 0xA000 | pit;
if (!_audiotnote)
{
REG_OPLOUT = 0xB000 | (_audiotblock | 0x20);
_audiotnote = true;
}
}
_audiotptr++;
_audiotlen--;
if (_audiotlen <= 0)
{
_audiotlen = 0;
REG_OPLOUT = 0xB000 | _audiotblock;
_audiotpriority = 0;
break;
}
}
}
if (!_imfptr)
{
if (nextVBlank)
nextVBlank();
return;
}
IMF_Service();
if (nextVBlank)
nextVBlank();
}
void IMF_Install(void(*next)(void))
{
if (next == NULL)
next = interface->vBlank;
nextVBlank = next;
interface->vBlank = IMF_Play;
}
void IMF_Remove()
{
interface->vBlank = nextVBlank;
nextVBlank = NULL;
}
int IMF_LoadSong(const uint16_t *sauce, bool loop)
{
imfdata = sauce;
if (imfdata)
{
_imfptr = imfdata;
_imfwait = 0;
_imfsize = (*_imfptr++) & 0xFCFF;
BYTESWAP(_imfsize);
}
else
{
_imfptr = 0;
}
imfLoop = loop;
return 0;
}
void IMF_AudioT(const void *sauce)
{
audiot* newsnd = (audiot*)sauce;
uint16_t pri = newsnd->priority;
uint32_t len = newsnd->length;
BYTESWAP(len);
BYTESWAP(pri);
if (pri < _audiotpriority)
return;
_audiot = newsnd;
_audiotpriority = pri;
const uint8_t regs[] =
{
0x20, 0x23, 0x40, 0x43, 0x60, 0x63, 0x80, 0x83, 0xE0, 0xE3, 0xC0
};
for (int i = 0; i < 11; i++)
REG_OPLOUT = (regs[i] << 8) | _audiot->settings[i];
_audiotptr = 0;
_audiotlen = len;
_audiotblock = (_audiot->octave & 7) << 2;
_audiotnote = false;
}