summaryrefslogtreecommitdiff
path: root/plugins/adplug/adplug/jbm.cpp
blob: 0990001b91719dc4a13431fbb583e35f96164952 (plain)
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
/*
 * Adplug - Replayer for many OPL2/OPL3 audio file formats.
 * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Johannes Bjerregaard's JBM Adlib Music Format player for AdPlug
 * Written by Dennis Lindroos <lindroos@nls.fi>, February-March 2007
 * - Designed and coded from scratch (only frequency-table taken from MUSIC.BIN)
 * - The percussion mode is buggy (?) but i'm not good enough to find them
 *   and honestly i think the melodic-mode tunes are much better ;)
 *
 * This version doesn't use the binstr.h functions (coded with custom func.)
 * This is my first attempt on writing a musicplayer for AdPlug, and i'm not
 * coding C++ very often.. 
 *
 * Released under the terms of the GNU General Public License.
 */

#include "jbm.h"

static const unsigned short notetable[96] = {
  0x0158, 0x016d, 0x0183, 0x019a, 0x01b2, 0x01cc, 0x01e7, 0x0204,
  0x0223, 0x0244, 0x0266, 0x028b, 0x0558, 0x056d, 0x0583, 0x059a,
  0x05b2, 0x05cc, 0x05e7, 0x0604, 0x0623, 0x0644, 0x0666, 0x068b,
  0x0958, 0x096d, 0x0983, 0x099a, 0x09b2, 0x09cc, 0x09e7, 0x0a04,
  0x0a23, 0x0a44, 0x0a66, 0x0a8b, 0x0d58, 0x0d6d, 0x0d83, 0x0d9a,
  0x0db2, 0x0dcc, 0x0de7, 0x0e04, 0x0e23, 0x0e44, 0x0e66, 0x0e8b,
  0x1158, 0x116d, 0x1183, 0x119a, 0x11b2, 0x11cc, 0x11e7, 0x1204,
  0x1223, 0x1244, 0x1266, 0x128b, 0x1558, 0x156d, 0x1583, 0x159a,
  0x15b2, 0x15cc, 0x15e7, 0x1604, 0x1623, 0x1644, 0x1666, 0x168b,
  0x1958, 0x196d, 0x1983, 0x199a, 0x19b2, 0x19cc, 0x19e7, 0x1a04,
  0x1a23, 0x1a44, 0x1a66, 0x1a8b, 0x1d58, 0x1d6d, 0x1d83, 0x1d9a,
  0x1db2, 0x1dcc, 0x1de7, 0x1e04, 0x1e23, 0x1e44, 0x1e66, 0x1e8b
};

static const unsigned char percmx_tab[4] = { 0x14, 0x12, 0x15, 0x11 };
static const unsigned char perchn_tab[5] = { 6, 7, 8, 8, 7 };
static unsigned char percmaskoff[5] = { 0xef, 0xf7, 0xfb, 0xfd, 0xfe };
static unsigned char percmaskon[5] =  { 0x10, 0x08, 0x04, 0x02, 0x01 };

static inline unsigned short GET_WORD(unsigned char *b, int x)
{
  return ((unsigned short)(b[x+1] << 8) | b[x]);
}

/*** public methods *************************************/

CPlayer *CjbmPlayer::factory(Copl *newopl)
{
  return new CjbmPlayer(newopl);
}

bool CjbmPlayer::load(const std::string &filename, const CFileProvider &fp)
{
  binistream	*f = fp.open(filename); if(!f) return false;
  int		filelen = fp.filesize(f);
  int		i;

  if (!filelen || !fp.extension(filename, ".jbm")) goto loaderr;

  // Allocate memory buffer m[] and read entire file into it

  m = new unsigned char[filelen];
  if (f->readString((char *)m, filelen) != filelen) goto loaderr;

  fp.close(f);

  // The known .jbm files always seem to start with the number 0x0002

  if (GET_WORD(m, 0) != 0x0002)
    return false;

  // Song tempo

  i = GET_WORD(m, 2);
  timer = 1193810.0 / (i ? i : 0xffff);

  seqtable = GET_WORD(m, 4);
  instable = GET_WORD(m, 6);

  // The flags word has atleast 1 bit, the Adlib's rhythm mode, but
  // currently we don't support that :(

  flags = GET_WORD(m, 8);

  // Instrument datas are directly addressed with m[] 

  inscount = (filelen - instable) >> 4;

  // Voice' and sequence pointers

  seqcount = 0xffff;
  for (i = 0; i < 11; i++) {
    voice[i].trkpos = voice[i].trkstart = GET_WORD(m, 10 + (i<<1));
    if (voice[i].trkpos && voice[i].trkpos < seqcount)
      seqcount = voice[i].trkpos;
  }
  seqcount = (seqcount - seqtable) >> 1;
  sequences = new unsigned short[seqcount];
  for (i = 0; i < seqcount; i++) 
    sequences[i] = GET_WORD(m, seqtable + (i<<1));

  rewind(0);
  return true;
 loaderr:
  fp.close(f);
  return false;
}

bool CjbmPlayer::update()
{
  short c, spos, frq;

  for (c = 0; c < 11; c++) {
    if (!voice[c].trkpos)		// Unused channel
      continue;

    if (--voice[c].delay)
      continue;

    // Turn current note/percussion off

    if (voice[c].note&0x7f)
      opl_noteonoff(c, &voice[c], 0);

    // Process events until we have a note

    spos = voice[c].seqpos;
    while(!voice[c].delay) {
      switch(m[spos]) {
      case 0xFD:	// Set Instrument
	voice[c].instr = m[spos+1];
	set_opl_instrument(c, &voice[c]);
	spos+=2;
	break;
      case 0xFF:	// End of Sequence
	voice[c].seqno = m[++voice[c].trkpos];
	if (voice[c].seqno == 0xff) {
	  voice[c].trkpos = voice[c].trkstart;
	  voice[c].seqno = m[voice[c].trkpos];
	  //voicemask &= 0x7ff-(1<<c);
	  voicemask &= ~(1<<c);
	}
	spos = voice[c].seqpos = sequences[voice[c].seqno];
	break;
      default:	// Note Event
	if ((m[spos] & 127) > 95)
	  return 0;

	voice[c].note = m[spos];
	voice[c].vol = m[spos+1];
	voice[c].delay =
	  (m[spos+2] + (m[spos+3]<<8)) + 1;

	frq = notetable[voice[c].note&127];
	voice[c].frq[0] = (unsigned char)frq;
	voice[c].frq[1] = frq >> 8;
	spos+=4;
      }
    }
    voice[c].seqpos = spos;

    // Write new volume to the carrier operator, or percussion

    if (flags&1 && c > 6)
      opl->write(0x40 + percmx_tab[c-7], voice[c].vol ^ 0x3f);
    else
      opl->write(0x43 + op_table[c], voice[c].vol ^ 0x3f);

    // Write new frequencies and Gate bit

    opl_noteonoff(c, &voice[c], !(voice[c].note & 0x80));
  }
  return (voicemask);
}

void CjbmPlayer::rewind(int subsong)
{
  int c;

  voicemask = 0;

  for (c = 0; c < 11; c++) {
    voice[c].trkpos = voice[c].trkstart;

    if (!voice[c].trkpos) continue;

    voicemask |= (1<<c);

    voice[c].seqno = m[voice[c].trkpos];
    voice[c].seqpos = sequences[voice[c].seqno];

    voice[c].note = 0;
    voice[c].delay = 1;
  }

  opl->init();
  opl->write(0x01, 32);

  // Set rhythm mode if flags bit #0 is set
  // AM and Vibrato are full depths (taken from DosBox RAW output)
  bdreg = 0xC0 | (flags&1)<<5;

  opl->write(0xbd, bdreg);

#if 0
  if (flags&1) {
    voice[7].frq[0] = 0x58; voice[7].frq[1] = 0x09; // XXX
    voice[8].frq[0] = 0x04; voice[8].frq[1] = 0x0a; // XXX
    opl_noteonoff(7, &voice[7], 0);
    opl_noteonoff(8, &voice[8], 0);
  }
#endif

  return;
}

/*** private methods ************************************/

void CjbmPlayer::opl_noteonoff(int channel, JBMVoice *v, bool state)
{
  if (flags&1 && channel > 5) {
    // Percussion
    opl->write(0xa0 + perchn_tab[channel-6], voice[channel].frq[0]);
    opl->write(0xb0 + perchn_tab[channel-6], voice[channel].frq[1]);
    opl->write(0xbd,
	       state ? bdreg | percmaskon[channel-6] :
	       bdreg & percmaskoff[channel-6]);
  } else {
    // Melodic mode or Rhythm mode melodic channels
    opl->write(0xa0 + channel, voice[channel].frq[0]);
    opl->write(0xb0 + channel,
	       state ? voice[channel].frq[1] | 0x20 :
	       voice[channel].frq[1] & 0x1f);
  }
  return;
}


void CjbmPlayer::set_opl_instrument(int channel, JBMVoice *v)
{
  short i = instable + (v->instr << 4);

  // Sanity check on instr number - or we'll be reading outside m[] !

  if (v->instr >= inscount)
    return;

  // For rhythm mode, multiplexed drums. I don't care about waveforms!
  if ((flags&1) & (channel > 6)) {
    opl->write(0x20 + percmx_tab[channel-7], m[i+0]);
    opl->write(0x40 + percmx_tab[channel-7], m[i+1] ^ 0x3f);
    opl->write(0x60 + percmx_tab[channel-7], m[i+2]);
    opl->write(0x80 + percmx_tab[channel-7], m[i+3]);

    opl->write(0xc0 + perchn_tab[channel-6], m[i+8]&15);
    return;
  }

  // AM/VIB/EG/KSR/FRQMUL, KSL/OUTPUT, ADSR for 1st operator
  opl->write(0x20 + op_table[channel], m[i+0]);
  opl->write(0x40 + op_table[channel], m[i+1] ^ 0x3f);
  opl->write(0x60 + op_table[channel], m[i+2]);
  opl->write(0x80 + op_table[channel], m[i+3]);

  // AM/VIB/EG/KSR/FRQMUL, KSL/OUTPUT, ADSR for 2nd operator
  opl->write(0x23 + op_table[channel], m[i+4]);
  opl->write(0x43 + op_table[channel], m[i+5] ^ 0x3f);
  opl->write(0x63 + op_table[channel], m[i+6]);
  opl->write(0x83 + op_table[channel], m[i+7]);

  // WAVEFORM for operators
  opl->write(0xe0 + op_table[channel], (m[i+8]>>4)&3);
  opl->write(0xe3 + op_table[channel], (m[i+8]>>6)&3);

  // FEEDBACK/FM mode
  opl->write(0xc0 + channel, m[i+8]&15);
	
  return;
}