- Added support for dumping from RAW/DRO/IMF files, so now anything that

can be played as OPL can also be dumped.
- Removed the opl_enable cvar, since OPL playback is now selectable as just
  another MIDI device.
- Added support for DRO playback and dual-chip RAW playback.
- Removed MUS support from OPLMUSSong, since using the OPLMIDIDevice with
  MUSSong2 works just as well. There are still lots of leftover bits in
  the class that should probably be removed at some point, too.
- Added dual-chip dumping support for the RAW format.
- Added DosBox Raw OPL (.DRO) dumping support. For whatever reason,
  in_adlib calculates the song length for this format wrong, even though
  the exact length is stored right in the header. (But in_adlib seems buggy
  in general; too bad it's the only Windows version of Adplug that seems to
  exist.)
- Rewrote the OPL dumper to work with MIDI as well as MUS.


SVN r872 (trunk)
This commit is contained in:
Randy Heit 2008-04-03 02:31:39 +00:00
parent 298e465e22
commit 3a5afd1418
18 changed files with 846 additions and 456 deletions

View file

@ -1,3 +1,24 @@
April 2, 2008
- Added support for dumping from RAW/DRO/IMF files, so now anything that
can be played as OPL can also be dumped.
- Removed the opl_enable cvar, since OPL playback is now selectable as just
another MIDI device.
April 1, 2008
- Added support for DRO playback and dual-chip RAW playback.
March 30, 2008
- Removed MUS support from OPLMUSSong, since using the OPLMIDIDevice with
MUSSong2 works just as well. There are still lots of leftover bits in
the class that should probably be removed at some point, too.
- Added dual-chip dumping support for the RAW format.
- Added DosBox Raw OPL (.DRO) dumping support. For whatever reason,
in_adlib calculates the song length for this format wrong, even though
the exact length is stored right in the header. (But in_adlib seems buggy
in general; too bad it's the only Windows version of Adplug that seems to
exist.)
- Rewrote the OPL dumper to work with MIDI as well as MUS.
March 30, 2008 (Changes by Graf Zahl)
- Changed: When the screen is being deleted the 'screen' variable should be
set to NULL before performing the delete. Otherwise, in some abnormal
@ -5904,7 +5925,7 @@ February 9, 2005
took me a while to figure out the problem, ZDoom can now play raw OPL songs.
February 7, 2005
- Added the writeopl command to write a song in RDosPlay raw OPL format. It's usage is:
- Added the writeopl command to write a song in RDosPlay raw OPL format. Its usage is:
writeopl [songname] <filename>
If the currently playing song is a MUS song being played through the OPL emulation,
then you just need to specify the filename. Otherwise, you need to provide the

View file

@ -1266,7 +1266,6 @@ static menu_t SoundMenu =
*
*=======================================*/
EXTERN_CVAR (Bool, opl_enable)
EXTERN_CVAR (Bool, opl_onechip)
static menuitem_t AdvSoundItems[] =

View file

@ -75,17 +75,6 @@ Revision History:
#endif
/* output final shift */
#if (OPL_SAMPLE_BITS==16)
#define FINAL_SH (0)
#define MAXOUT (+32767)
#define MINOUT (-32768)
#else
#define FINAL_SH (8)
#define MAXOUT (+127)
#define MINOUT (-128)
#endif
#define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */
#define EG_SH 16 /* 16.16 fixed point (EG timing) */
#define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */

View file

@ -3,8 +3,8 @@
#include "zstring.h"
/* select output bits size of output : 8 or 16 */
#define OPL_SAMPLE_BITS 16
// Multiplying OPL_SAMPLE_RATE by ADLIB_CLOCK_MUL gives the number
// Adlib clocks per second, as used by the RAWADATA file format.
/* compiler dependence */
#ifndef OSD_CPU_H
@ -17,13 +17,6 @@ typedef signed short INT16; /* signed 16bit */
typedef signed int INT32; /* signed 32bit */
#endif
#if (OPL_SAMPLE_BITS==16)
typedef INT16 OPLSAMPLE;
#endif
#if (OPL_SAMPLE_BITS==8)
typedef INT8 OPLSAMPLE;
#endif
typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec);
typedef void (*OPL_IRQHANDLER)(int param,int irq);

View file

@ -1,124 +0,0 @@
/*
* Name: MUS Playing kernel
* Project: MUS File Player Library
* Version: 1.70
* Author: Vladimir Arnost (QA-Software)
* Last revision: Oct-28-1995
* Compiler: Borland C++ 3.1, Watcom C/C++ 10.0
*
*/
/*
* Revision History:
*
* Aug-8-1994 V1.00 V.Arnost
* Written from scratch
* Aug-9-1994 V1.10 V.Arnost
* Some minor changes to improve sound quality. Tried to add
* stereo sound capabilities, but failed to -- my SB Pro refuses
* to switch to stereo mode.
* Aug-13-1994 V1.20 V.Arnost
* Stereo sound fixed. Now works also with Sound Blaster Pro II
* (chip OPL3 -- gives 18 "stereo" (ahem) channels).
* Changed code to handle properly notes without volume.
* (Uses previous volume on given channel.)
* Added cyclic channel usage to avoid annoying clicking noise.
* Aug-17-1994 V1.30 V.Arnost
* Completely rewritten time synchronization. Now the player runs
* on IRQ 8 (RTC Clock - 1024 Hz).
* Aug-28-1994 V1.40 V.Arnost
* Added Adlib and SB Pro II detection.
* Fixed bug that caused high part of 32-bit registers (EAX,EBX...)
* to be corrupted.
* Oct-30-1994 V1.50 V.Arnost
* Tidied up the source code
* Added C key - invoke COMMAND.COM
* Added RTC timer interrupt flag check (0000:04A0)
* Added BLASTER environment variable parsing
* FIRST PUBLIC RELEASE
* Apr-16-1995 V1.60 V.Arnost
* Moved into separate source file MUSLIB.C
* May-01-1995 V1.61 V.Arnost
* Added system timer (IRQ 0) support
* Jul-12-1995 V1.62 V.Arnost
* OPL2/OPL3-specific code moved to module MLOPL.C
* Module MUSLIB.C renamed to MLKERNEL.C
* Aug-04-1995 V1.63 V.Arnost
* Fixed stack-related bug occuring in big-code models in Watcom C
* Aug-16-1995 V1.64 V.Arnost
* Stack size changed from 256 to 512 words because of stack
* underflows caused by AWE32 driver
* Aug-28-1995 V1.65 V.Arnost
* Fixed a serious bug that caused the player to generate an
* exception in AWE32 driver under DOS/4GW: Register ES contained
* garbage instead of DGROUP. The compiler-generated prolog of
* interrupt handlers doesn't set ES register at all, thus any
* STOS/MOVS/SCAS/CMPS instruction used within the int. handler
* crashes the program.
* Oct-28-1995 V1.70 V.Arnost
* System-specific timer code moved separate modules
*/
#include "muslib.h"
char MLversion[] = "MUS Lib V"MLVERSIONSTR;
char MLcopyright[] = "Copyright (c) 1994-1996 QA-Software";
/* Program */
int musicBlock::playTick()
{
int delay = 0;
while (delay == 0)
{
uchar data = *score++;
uchar command = (data >> 4) & 7;
uchar channel = data & 0x0F;
uchar last = data & 0x80;
switch (command)
{
case 0: // release note
playingcount--;
OPLreleaseNote(channel, *score++);
break;
case 1: { // play note
uchar note = *score++;
playingcount++;
if (note & 0x80) // note with volume
OPLplayNote(channel, note & 0x7F, *score++);
else
OPLplayNote(channel, note, -1);
} break;
case 2: // pitch wheel
// MUS pitch wheel is 8 bits, but MIDI is 14
OPLpitchWheel(channel, *score++ << (14 - 8));
break;
case 3: // system event (valueless controller)
OPLchangeControl(channel, *score++, 0);
break;
case 4: { // change control
uchar ctrl = *score++;
uchar value = *score++;
OPLchangeControl(channel, ctrl, value);
} break;
case 6: // end
return 0;
case 5: // ???
case 7: // ???
break;
}
if (last)
{
uchar t;
do
{
t = *score++;
delay = (delay << 7) | (t & 127);
} while (t & 128);
}
}
return delay;
}

View file

@ -44,6 +44,18 @@
#include "muslib.h"
#include "fmopl.h"
OPLio::~OPLio()
{
}
void OPLio::SetClockRate(double samples_per_tick)
{
}
void OPLio::WriteDelay(int ticks)
{
}
void OPLio::OPLwriteReg(int which, uint reg, uchar data)
{
YM3812Write (which, 0, reg);
@ -106,64 +118,73 @@ static WORD frequencies[] =
0x1d9, 0x1da, 0x1db, 0x1dc, 0x1dd, 0x1de, 0x1de, 0x1df, 0x1e0, 0x1e1, 0x1e2, 0x1e3,
0x1e4, 0x1e5, 0x1e5, 0x1e6, 0x1e7, 0x1e8, 0x1e9, 0x1ea, 0x1eb, 0x1ec, 0x1ed, 0x1ed,
0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x1f2, 0x1f3, 0x1f4, 0x1f5, 0x1f6, 0x1f6, 0x1f7, 0x1f8,
0x1f9, 0x1fa, 0x1fb, 0x1fc, 0x1fd, 0x1fe, 0x1ff, 0x200, 0x201, 0x201, 0x202, 0x203,
0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20a, 0x20b, 0x20c, 0x20d, 0x20e, 0x20f,
0x210, 0x210, 0x211, 0x212, 0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21a,
0x21b, 0x21c, 0x21d, 0x21e, 0x21f, 0x220, 0x221, 0x222, 0x223, 0x224, 0x225, 0x226,
0x227, 0x228, 0x229, 0x22a, 0x22b, 0x22c, 0x22d, 0x22e, 0x22f, 0x230, 0x231, 0x232,
0x233, 0x234, 0x235, 0x236, 0x237, 0x238, 0x239, 0x23a, 0x23b, 0x23c, 0x23d, 0x23e,
0x23f, 0x240, 0x241, 0x242, 0x244, 0x245, 0x246, 0x247, 0x248, 0x249, 0x24a, 0x24b,
0x24c, 0x24d, 0x24e, 0x24f, 0x250, 0x251, 0x252, 0x253, 0x254, 0x256, 0x257, 0x258,
0x259, 0x25a, 0x25b, 0x25c, 0x25d, 0x25e, 0x25f, 0x260, 0x262, 0x263, 0x264, 0x265,
0x266, 0x267, 0x268, 0x269, 0x26a, 0x26c, 0x26d, 0x26e, 0x26f, 0x270, 0x271, 0x272,
0x273, 0x275, 0x276, 0x277, 0x278, 0x279, 0x27a, 0x27b, 0x27d, 0x27e, 0x27f, 0x280,
0x281, 0x282, 0x284, 0x285, 0x286, 0x287, 0x288, 0x289, 0x28b, 0x28c, 0x28d, 0x28e,
0x28f, 0x290, 0x292, 0x293, 0x294, 0x295, 0x296, 0x298, 0x299, 0x29a, 0x29b, 0x29c,
0x29e, 0x29f, 0x2a0, 0x2a1, 0x2a2, 0x2a4, 0x2a5, 0x2a6, 0x2a7, 0x2a9, 0x2aa, 0x2ab,
0x2ac, 0x2ae, 0x2af, 0x2b0, 0x2b1, 0x2b2, 0x2b4, 0x2b5, 0x2b6, 0x2b7, 0x2b9, 0x2ba,
0x2bb, 0x2bd, 0x2be, 0x2bf, 0x2c0, 0x2c2, 0x2c3, 0x2c4, 0x2c5, 0x2c7, 0x2c8, 0x2c9,
0x2cb, 0x2cc, 0x2cd, 0x2ce, 0x2d0, 0x2d1, 0x2d2, 0x2d4, 0x2d5, 0x2d6, 0x2d8, 0x2d9,
0x2da, 0x2dc, 0x2dd, 0x2de, 0x2e0, 0x2e1, 0x2e2, 0x2e4, 0x2e5, 0x2e6, 0x2e8, 0x2e9,
0x2ea, 0x2ec, 0x2ed, 0x2ee, 0x2f0, 0x2f1, 0x2f2, 0x2f4, 0x2f5, 0x2f6, 0x2f8, 0x2f9,
0x2fb, 0x2fc, 0x2fd, 0x2ff, 0x300, 0x302, 0x303, 0x304, 0x306, 0x307, 0x309, 0x30a,
0x30b, 0x30d, 0x30e, 0x310, 0x311, 0x312, 0x314, 0x315, 0x317, 0x318, 0x31a, 0x31b,
0x31c, 0x31e, 0x31f, 0x321, 0x322, 0x324, 0x325, 0x327, 0x328, 0x329, 0x32b, 0x32c,
0x32e, 0x32f, 0x331, 0x332, 0x334, 0x335, 0x337, 0x338, 0x33a, 0x33b, 0x33d, 0x33e,
0x340, 0x341, 0x343, 0x344, 0x346, 0x347, 0x349, 0x34a, 0x34c, 0x34d, 0x34f, 0x350,
0x352, 0x353, 0x355, 0x357, 0x358, 0x35a, 0x35b, 0x35d, 0x35e, 0x360, 0x361, 0x363,
0x365, 0x366, 0x368, 0x369, 0x36b, 0x36c, 0x36e, 0x370, 0x371, 0x373, 0x374, 0x376,
0x378, 0x379, 0x37b, 0x37c, 0x37e, 0x380, 0x381, 0x383, 0x384, 0x386, 0x388, 0x389,
0x38b, 0x38d, 0x38e, 0x390, 0x392, 0x393, 0x395, 0x397, 0x398, 0x39a, 0x39c, 0x39d,
0x39f, 0x3a1, 0x3a2, 0x3a4, 0x3a6, 0x3a7, 0x3a9, 0x3ab, 0x3ac, 0x3ae, 0x3b0, 0x3b1,
0x3b3, 0x3b5, 0x3b7, 0x3b8, 0x3ba, 0x3bc, 0x3bd, 0x3bf, 0x3c1, 0x3c3, 0x3c4, 0x3c6,
0x3c8, 0x3ca, 0x3cb, 0x3cd, 0x3cf, 0x3d1, 0x3d2, 0x3d4, 0x3d6, 0x3d8, 0x3da, 0x3db,
0x3dd, 0x3df, 0x3e1, 0x3e3, 0x3e4, 0x3e6, 0x3e8, 0x3ea, 0x3ec, 0x3ed, 0x3ef, 0x3f1,
0x3f3, 0x3f5, 0x3f6, 0x3f8, 0x3fa, 0x3fc, 0x3fe, 0x36c, 0x388
0x1f9, 0x1fa, 0x1fb, 0x1fc, 0x1fd, 0x1fe, 0x1ff, 0x200,
0x201, 0x201, 0x202, 0x203, 0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20a, 0x20b, 0x20c, 0x20d, 0x20e, 0x20f,
0x210, 0x210, 0x211, 0x212, 0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21a, 0x21b, 0x21c, 0x21d, 0x21e,
0x21f, 0x220, 0x221, 0x222, 0x223, 0x224, 0x225, 0x226, 0x227, 0x228, 0x229, 0x22a, 0x22b, 0x22c, 0x22d, 0x22e,
0x22f, 0x230, 0x231, 0x232, 0x233, 0x234, 0x235, 0x236, 0x237, 0x238, 0x239, 0x23a, 0x23b, 0x23c, 0x23d, 0x23e,
0x23f, 0x240, 0x241, 0x242, 0x244, 0x245, 0x246, 0x247, 0x248, 0x249, 0x24a, 0x24b, 0x24c, 0x24d, 0x24e, 0x24f,
0x250, 0x251, 0x252, 0x253, 0x254, 0x256, 0x257, 0x258, 0x259, 0x25a, 0x25b, 0x25c, 0x25d, 0x25e, 0x25f, 0x260,
0x262, 0x263, 0x264, 0x265, 0x266, 0x267, 0x268, 0x269, 0x26a, 0x26c, 0x26d, 0x26e, 0x26f, 0x270, 0x271, 0x272,
0x273, 0x275, 0x276, 0x277, 0x278, 0x279, 0x27a, 0x27b, 0x27d, 0x27e, 0x27f, 0x280, 0x281, 0x282, 0x284, 0x285,
0x286, 0x287, 0x288, 0x289, 0x28b, 0x28c, 0x28d, 0x28e, 0x28f, 0x290, 0x292, 0x293, 0x294, 0x295, 0x296, 0x298,
0x299, 0x29a, 0x29b, 0x29c, 0x29e, 0x29f, 0x2a0, 0x2a1, 0x2a2, 0x2a4, 0x2a5, 0x2a6, 0x2a7, 0x2a9, 0x2aa, 0x2ab,
0x2ac, 0x2ae, 0x2af, 0x2b0, 0x2b1, 0x2b2, 0x2b4, 0x2b5, 0x2b6, 0x2b7, 0x2b9, 0x2ba, 0x2bb, 0x2bd, 0x2be, 0x2bf,
0x2c0, 0x2c2, 0x2c3, 0x2c4, 0x2c5, 0x2c7, 0x2c8, 0x2c9, 0x2cb, 0x2cc, 0x2cd, 0x2ce, 0x2d0, 0x2d1, 0x2d2, 0x2d4,
0x2d5, 0x2d6, 0x2d8, 0x2d9, 0x2da, 0x2dc, 0x2dd, 0x2de, 0x2e0, 0x2e1, 0x2e2, 0x2e4, 0x2e5, 0x2e6, 0x2e8, 0x2e9,
0x2ea, 0x2ec, 0x2ed, 0x2ee, 0x2f0, 0x2f1, 0x2f2, 0x2f4, 0x2f5, 0x2f6, 0x2f8, 0x2f9, 0x2fb, 0x2fc, 0x2fd, 0x2ff,
0x300, 0x302, 0x303, 0x304, 0x306, 0x307, 0x309, 0x30a, 0x30b, 0x30d, 0x30e, 0x310, 0x311, 0x312, 0x314, 0x315,
0x317, 0x318, 0x31a, 0x31b, 0x31c, 0x31e, 0x31f, 0x321, 0x322, 0x324, 0x325, 0x327, 0x328, 0x329, 0x32b, 0x32c,
0x32e, 0x32f, 0x331, 0x332, 0x334, 0x335, 0x337, 0x338, 0x33a, 0x33b, 0x33d, 0x33e, 0x340, 0x341, 0x343, 0x344,
0x346, 0x347, 0x349, 0x34a, 0x34c, 0x34d, 0x34f, 0x350, 0x352, 0x353, 0x355, 0x357, 0x358, 0x35a, 0x35b, 0x35d,
0x35e, 0x360, 0x361, 0x363, 0x365, 0x366, 0x368, 0x369, 0x36b, 0x36c, 0x36e, 0x370, 0x371, 0x373, 0x374, 0x376,
0x378, 0x379, 0x37b, 0x37c, 0x37e, 0x380, 0x381, 0x383, 0x384, 0x386, 0x388, 0x389, 0x38b, 0x38d, 0x38e, 0x390,
0x392, 0x393, 0x395, 0x397, 0x398, 0x39a, 0x39c, 0x39d, 0x39f, 0x3a1, 0x3a2, 0x3a4, 0x3a6, 0x3a7, 0x3a9, 0x3ab,
0x3ac, 0x3ae, 0x3b0, 0x3b1, 0x3b3, 0x3b5, 0x3b7, 0x3b8, 0x3ba, 0x3bc, 0x3bd, 0x3bf, 0x3c1, 0x3c3, 0x3c4, 0x3c6,
0x3c8, 0x3ca, 0x3cb, 0x3cd, 0x3cf, 0x3d1, 0x3d2, 0x3d4, 0x3d6, 0x3d8, 0x3da, 0x3db, 0x3dd, 0x3df, 0x3e1, 0x3e3,
0x3e4, 0x3e6, 0x3e8, 0x3ea, 0x3ec, 0x3ed, 0x3ef, 0x3f1, 0x3f3, 0x3f5, 0x3f6, 0x3f8, 0x3fa, 0x3fc, 0x3fe, 0x36c
};
/*
* Write frequency/octave/keyon data to a channel
* [RH] This is totally different from the original MUS library code
* but matches exactly what DMX does. I haven't a clue why there are 284
* special bytes at the beginning of the table for the first few notes.
* That last byte in the table doesn't look right, either, but that's what
* it really is.
*/
void OPLio::OPLwriteFreq(uint channel, uint freq, uint octave, uint keyon)
void OPLio::OPLwriteFreq(uint channel, uint note, uint pitch, uint keyon)
{
int i;
int j = (freq<<5) + octave;
i = 0;
int octave = 0;
int j = (note << 5) + pitch;
if (j < 0)
{
j = 0;
}
else if (j >= 0x11C)
else if (j >= 284)
{
j -= 0x11C;
i = j / 0x180;
if (i > 7)
j -= 284;
octave = j / (32*12);
if (octave > 7)
{
i = 7;
octave = 7;
}
j = (j % 0x180) + 0x11C;
j = (j % (32*12)) + 284;
}
i = frequencies[j] | (i << 10);
int i = frequencies[j] | (octave << 10);
OPLwriteValue (0xA0, channel, (BYTE)i);
OPLwriteValue (0xB0, channel, (BYTE)(i>>8)|(keyon<<5));
@ -277,20 +298,12 @@ void OPLio::OPLshutup(void)
/*
* Initialize hardware upon startup
*/
int OPLio::OPLinit(uint numchips, uint rate)
int OPLio::OPLinit(uint numchips)
{
if (!YM3812Init (numchips, 3579545, rate))
if (!YM3812Init (numchips, 3579545, int(OPL_SAMPLE_RATE)))
{
uint i;
OPLchannels = OPL2CHANNELS*numchips;
for (i = 0; i < numchips; ++i)
{
OPLwriteReg (i, 0x01, 0x20); // enable Waveform Select
OPLwriteReg (i, 0x0B, 0x40); // turn off CSW mode
OPLwriteReg (i, 0xBD, 0x00); // set vibrato/tremolo depth to low, set melodic mode
}
OPLshutup();
OPLchannels = OPL2CHANNELS * numchips;
OPLwriteInitState();
return 0;
}
else
@ -299,6 +312,17 @@ int OPLio::OPLinit(uint numchips, uint rate)
}
}
void OPLio::OPLwriteInitState()
{
for (uint i = 0; i < OPLchannels / OPL2CHANNELS; ++i)
{
OPLwriteReg (i, 0x01, 0x20); // enable Waveform Select
OPLwriteReg (i, 0x0B, 0x40); // turn off CSW mode
OPLwriteReg (i, 0xBD, 0x00); // set vibrato/tremolo depth to low, set melodic mode
}
OPLshutup();
}
/*
* Deinitialize hardware before shutdown
*/

View file

@ -40,6 +40,7 @@
#include "doomdef.h"
#include "m_swap.h"
#include "w_wad.h"
#include "fmopl.h"
// MACROS ------------------------------------------------------------------
@ -105,7 +106,7 @@ OPLMIDIDevice::~OPLMIDIDevice()
int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata)
{
if (io == NULL || io->OPLinit(TwoChips + 1, uint(OPL_SAMPLE_RATE)))
if (io == NULL || io->OPLinit(TwoChips + 1))
{
return 1;
}
@ -209,6 +210,7 @@ int OPLMIDIDevice::SetTimeDiv(int timediv)
void OPLMIDIDevice::CalcTickRate()
{
SamplesPerTick = OPL_SAMPLE_RATE / (1000000.0 / Tempo) / Division;
io->SetClockRate(SamplesPerTick);
}
//==========================================================================

View file

@ -0,0 +1,372 @@
/*
** music_opl_mididevice.cpp
** Writes raw OPL commands from the emulated OPL MIDI output to disk.
**
**---------------------------------------------------------------------------
** Copyright 2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#include "i_musicinterns.h"
#include "templates.h"
#include "doomdef.h"
#include "m_swap.h"
#include "w_wad.h"
#include "fmopl.h"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// OPLDumperMIDIDevice Constructor
//
//==========================================================================
OPLDumperMIDIDevice::OPLDumperMIDIDevice(const char *filename)
{
// Replace the standard OPL device with a disk writer.
delete io;
io = new DiskWriterIO(filename);
}
//==========================================================================
//
// OPLDumperMIDIDevice Destructor
//
//==========================================================================
OPLDumperMIDIDevice::~OPLDumperMIDIDevice()
{
}
//==========================================================================
//
// OPLDumperMIDIDevice :: Resume
//
//==========================================================================
int OPLDumperMIDIDevice::Resume()
{
int time;
time = PlayTick();
while (time != 0)
{
io->WriteDelay(time);
time = PlayTick();
}
return 0;
}
//==========================================================================
//
// OPLDumperMIDIDevice :: Stop
//
//==========================================================================
void OPLDumperMIDIDevice::Stop()
{
}
//==========================================================================
//
// DiskWriterIO Constructor
//
//==========================================================================
DiskWriterIO::DiskWriterIO(const char *filename)
: Filename(filename)
{
}
//==========================================================================
//
// DiskWriterIO Destructor
//
//==========================================================================
DiskWriterIO::~DiskWriterIO()
{
OPLdeinit();
}
//==========================================================================
//
// DiskWriterIO :: OPLinit
//
//==========================================================================
int DiskWriterIO::OPLinit(uint numchips)
{
// If the file extension is unknown or not present, the default format
// is RAW. Otherwise, you can use DRO.
if (Filename.Len() < 5 || stricmp(&Filename[Filename.Len() - 4], ".dro") != 0)
{
Format = FMT_RDOS;
}
else
{
Format = FMT_DOSBOX;
}
File = fopen(Filename, "wb");
if (File == NULL)
{
Printf("Could not open %s for writing.\n", Filename.GetChars());
return -1;
}
if (Format == FMT_RDOS)
{
fwrite("RAWADATA\0", 1, 10, File);
NeedClockRate = true;
}
else
{
fwrite("DBRAWOPL"
"\0\0" // Minor version number
"\1\0" // Major version number
"\0\0\0\0" // Total milliseconds
"\0\0\0", // Total data
1, 20, File);
if (numchips == 1)
{
fwrite("\0\0\0", 1, 4, File); // Single OPL-2
}
else
{
fwrite("\2\0\0", 1, 4, File); // Dual OPL-2
}
NeedClockRate = false;
}
TimePerTick = 0;
TickMul = 1;
CurTime = 0;
CurIntTime = 0;
CurChip = 0;
OPLchannels = OPL2CHANNELS * numchips;
OPLwriteInitState();
return 0;
}
//==========================================================================
//
// DiskWriterIO :: OPLdeinit
//
//==========================================================================
void DiskWriterIO::OPLdeinit()
{
if (File != NULL)
{
if (Format == FMT_RDOS)
{
WORD endmark = 65535;
fwrite(&endmark, 2, 1, File);
}
else
{
long where_am_i = ftell(File);
DWORD len[2];
fseek(File, 12, SEEK_SET);
len[0] = LittleLong(CurIntTime);
len[1] = LittleLong(where_am_i - 24);
fwrite(len, 4, 2, File);
}
fclose(File);
File = NULL;
}
}
//==========================================================================
//
// DiskWriterIO :: OPLwriteReg
//
//==========================================================================
void DiskWriterIO::OPLwriteReg(int which, uint reg, uchar data)
{
SetChip(which);
if (Format == FMT_RDOS)
{
if (reg != 0 && reg != 2 && (reg != 255 || data != 255))
{
BYTE cmd[2] = { data, reg };
fwrite(cmd, 1, 2, File);
}
}
else
{
BYTE cmd[3] = { 4, reg, data };
fwrite (cmd + (reg > 4), 1, 3 - (reg > 4), File);
}
}
//==========================================================================
//
// DiskWriterIO :: SetChip
//
//==========================================================================
void DiskWriterIO :: SetChip(int chipnum)
{
assert(chipnum == 0 || chipnum == 1);
if (chipnum != CurChip)
{
CurChip = chipnum;
if (Format == FMT_RDOS)
{
BYTE switcher[2] = { chipnum + 1, 2 };
fwrite(switcher, 1, 2, File);
}
else
{
BYTE switcher = chipnum + 2;
fwrite(&switcher, 1, 1, File);
}
}
}
//==========================================================================
//
// DiskWriterIO :: SetClockRate
//
//==========================================================================
void DiskWriterIO::SetClockRate(double samples_per_tick)
{
TimePerTick = samples_per_tick / OPL_SAMPLE_RATE * 1000.0;
if (Format == FMT_RDOS)
{
double clock_rate;
int clock_mul;
WORD clock_word;
clock_rate = samples_per_tick * ADLIB_CLOCK_MUL;
clock_mul = 1;
// The RDos raw format's clock rate is stored in a word. Therefore,
// the longest tick that can be stored is only ~55 ms.
while (clock_rate / clock_mul + 0.5 > 65535.0)
{
clock_mul++;
}
clock_word = WORD(clock_rate / clock_mul + 0.5);
if (NeedClockRate)
{ // Set the initial clock rate.
clock_word = LittleShort(clock_word);
fseek(File, 8, SEEK_SET);
fwrite(&clock_word, 2, 1, File);
fseek(File, 0, SEEK_END);
NeedClockRate = false;
}
else
{ // Change the clock rate in the middle of the song.
BYTE clock_change[4] = { 0, 2, clock_word & 255, clock_word >> 8 };
fwrite(clock_change, 1, 4, File);
}
}
}
//==========================================================================
//
// DiskWriterIO :: WriteDelay
//
//==========================================================================
void DiskWriterIO :: WriteDelay(int ticks)
{
if (ticks <= 0)
{
return;
}
if (Format == FMT_RDOS)
{ // RDos raw has very precise delays but isn't very efficient at
// storing long delays.
BYTE delay[2];
ticks *= TickMul;
delay[1] = 0;
while (ticks > 255)
{
ticks -= 255;
delay[0] = 255;
fwrite(delay, 1, 2, File);
}
delay[0] = BYTE(ticks);
fwrite(delay, 1, 2, File);
}
else
{ // DosBox only has millisecond-precise delays.
int delay;
CurTime += TimePerTick * ticks;
delay = int(CurTime + 0.5) - CurIntTime;
CurIntTime += delay;
while (delay > 65536)
{
BYTE cmd[3] = { 1, 255, 255 };
fwrite(cmd, 1, 2, File);
delay -= 65536;
}
delay--;
if (delay <= 255)
{
BYTE cmd[2] = { 0, BYTE(delay) };
fwrite(cmd, 1, 2, File);
}
else
{
assert(delay <= 65535);
BYTE cmd[3] = { 1, BYTE(delay & 255), BYTE(delay >> 8) };
fwrite(cmd, 1, 3, File);
}
}
}

View file

@ -130,7 +130,7 @@ struct OP2instrEntry {
};
#define FL_FIXED_PITCH 0x0001 // note has fixed pitch (see below)
#define FL_UNKNOWN 0x0002 // ??? (used in instrument #65 only)
#define FL_UNKNOWN 0x0002 // ??? (used in instrument #65 only)
#define FL_DOUBLE_VOICE 0x0004 // use two voices instead of one
@ -160,7 +160,7 @@ struct OPLdata {
};
struct OPLio {
virtual ~OPLio() {}
virtual ~OPLio();
void OPLwriteChannel(uint regbase, uint channel, uchar data1, uchar data2);
void OPLwriteValue(uint regbase, uint channel, uchar value);
@ -171,14 +171,43 @@ struct OPLio {
void OPLwritePan(uint channel, struct OPL2instrument *instr, int pan);
void OPLwriteInstrument(uint channel, struct OPL2instrument *instr);
void OPLshutup(void);
void OPLwriteInitState();
virtual int OPLinit(uint numchips, uint rate);
virtual int OPLinit(uint numchips);
virtual void OPLdeinit(void);
virtual void OPLwriteReg(int which, uint reg, uchar data);
virtual void SetClockRate(double samples_per_tick);
virtual void WriteDelay(int ticks);
uint OPLchannels;
};
struct DiskWriterIO : public OPLio
{
DiskWriterIO(const char *filename);
~DiskWriterIO();
int OPLinit(uint numchips);
void OPLdeinit();
void OPLwriteReg(int which, uint reg, uchar data);
void SetClockRate(double samples_per_tick);
void WriteDelay(int ticks);
void SetChip(int chipnum);
FILE *File;
FString Filename;
int Format;
bool NeedClockRate;
double TimePerTick; // In milliseconds
double CurTime;
int CurIntTime;
int TickMul;
int CurChip;
enum { FMT_RDOS, FMT_DOSBOX };
};
struct musicBlock {
musicBlock();
~musicBlock();
@ -193,8 +222,6 @@ struct musicBlock {
ulong MLtime;
int playTick();
void OPLplayNote(uint channel, uchar note, int volume);
void OPLreleaseNote(uint channel, uchar note);
void OPLpitchWheel(uint channel, int pitch);
@ -254,4 +281,7 @@ enum MUSctrl {
ctrlResetCtrls
};
#define OPL_SAMPLE_RATE 49716.0
#define ADLIB_CLOCK_MUL 24.0
#endif // __MUSLIB_H_

View file

@ -79,7 +79,7 @@ void OPLmusicBlock::ResetChips ()
TwoChips = !opl_onechip;
Serialize();
io->OPLdeinit ();
io->OPLinit (TwoChips + 1, uint(OPL_SAMPLE_RATE));
io->OPLinit (TwoChips + 1);
Unserialize();
}
@ -91,7 +91,7 @@ void OPLmusicBlock::Restart()
playingcount = 0;
}
OPLmusicFile::OPLmusicFile (FILE *file, char * musiccache, int len, int maxSamples)
OPLmusicFile::OPLmusicFile (FILE *file, char *musiccache, int len)
: ScoreLen (len)
{
if (io == NULL)
@ -115,29 +115,15 @@ OPLmusicFile::OPLmusicFile (FILE *file, char * musiccache, int len, int maxSampl
memcpy(scoredata, &musiccache[0], len);
}
if (io->OPLinit (TwoChips + 1, uint(OPL_SAMPLE_RATE)))
if (io->OPLinit (TwoChips + 1))
{
delete[] scoredata;
scoredata = NULL;
return;
}
// Check for MUS format
if (*(DWORD *)scoredata == MAKE_ID('M','U','S',0x1a))
{
FWadLump data = Wads.OpenLumpName ("GENMIDI");
if (0 != OPLloadBank (data))
{
delete[] scoredata;
scoredata = NULL;
return;
}
BlockForStats = this;
SamplesPerTick = OPL_SAMPLE_RATE / 140.0;
RawPlayer = NotRaw;
}
// Check for RDosPlay raw OPL format
else if (((DWORD *)scoredata)[0] == MAKE_ID('R','A','W','A') &&
if (((DWORD *)scoredata)[0] == MAKE_ID('R','A','W','A') &&
((DWORD *)scoredata)[1] == MAKE_ID('D','A','T','A'))
{
RawPlayer = RDosPlay;
@ -145,7 +131,16 @@ OPLmusicFile::OPLmusicFile (FILE *file, char * musiccache, int len, int maxSampl
{ // A clock speed of 0 is bad
*(WORD *)(scoredata + 8) = 0xFFFF;
}
SamplesPerTick = OPL_SAMPLE_RATE * LittleShort(*(WORD *)(scoredata + 8)) / 1193180.0;
SamplesPerTick = LittleShort(*(WORD *)(scoredata + 8)) / ADLIB_CLOCK_MUL;
}
// Check for DosBox OPL dump
else if (((DWORD *)scoredata)[0] == MAKE_ID('D','B','R','A') &&
((DWORD *)scoredata)[1] == MAKE_ID('W','O','P','L') &&
((DWORD *)scoredata)[2] == MAKE_ID(0,0,1,0))
{
RawPlayer = DosBox;
SamplesPerTick = OPL_SAMPLE_RATE / 1000;
ScoreLen = MIN<int>(len - 24, LittleLong(((DWORD *)scoredata)[4]));
}
// Check for modified IMF format (includes a header)
else if (((DWORD *)scoredata)[0] == MAKE_ID('A','D','L','I') &&
@ -202,14 +197,16 @@ void OPLmusicFile::SetLooping (bool loop)
void OPLmusicFile::Restart ()
{
OPLmusicBlock::Restart();
if (RawPlayer == NotRaw)
{
score = scoredata + ((MUSheader *)scoredata)->scoreStart;
}
else if (RawPlayer == RDosPlay)
WhichChip = 0;
if (RawPlayer == RDosPlay)
{
score = scoredata + 10;
SamplesPerTick = OPL_SAMPLE_RATE * LittleShort(*(WORD *)(scoredata + 8)) / 1193180.0;
SamplesPerTick = LittleShort(*(WORD *)(scoredata + 8)) / ADLIB_CLOCK_MUL;
}
else if (RawPlayer == DosBox)
{
score = scoredata + 24;
SamplesPerTick = OPL_SAMPLE_RATE / 1000;
}
else if (RawPlayer == IMF)
{
@ -226,6 +223,7 @@ void OPLmusicFile::Restart ()
score += 4; // Skip song length
}
}
io->SetClockRate(SamplesPerTick);
}
bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
@ -289,6 +287,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
else
{
prevEnded = false;
io->WriteDelay(next);
NextTickIn += SamplesPerTick * next;
assert (NextTickIn >= 0);
MLtime += next;
@ -303,11 +302,7 @@ int OPLmusicFile::PlayTick ()
{
BYTE reg, data;
if (RawPlayer == NotRaw)
{
return playTick ();
}
else if (RawPlayer == RDosPlay)
if (RawPlayer == RDosPlay)
{
while (score < scoredata + ScoreLen)
{
@ -325,9 +320,18 @@ int OPLmusicFile::PlayTick ()
case 2: // Speed change or OPL3 switch
if (data == 0)
{
SamplesPerTick = OPL_SAMPLE_RATE * LittleShort(*(WORD *)(score)) / 1193180.0;
SamplesPerTick = LittleShort(*(WORD *)(score)) / ADLIB_CLOCK_MUL;
io->SetClockRate(SamplesPerTick);
score += 2;
}
else if (data == 1)
{
WhichChip = 0;
}
else if (data == 2)
{
WhichChip = 1;
}
break;
case 0xFF: // End of song
@ -338,11 +342,55 @@ int OPLmusicFile::PlayTick ()
break;
default: // It's something to stuff into the OPL chip
io->OPLwriteReg (0, reg, data);
if (WhichChip == 0 || TwoChips)
{
io->OPLwriteReg(WhichChip, reg, data);
}
break;
}
}
}
else if (RawPlayer == DosBox)
{
while (score < scoredata + ScoreLen)
{
reg = *score++;
if (reg == 4)
{
reg = *score++;
data = *score++;
}
else if (reg == 0)
{ // One-byte delay
return *score++ + 1;
}
else if (reg == 1)
{ // Two-byte delay
int delay = score[0] + (score[1] << 8) + 1;
score += 2;
return delay;
}
else if (reg == 2)
{ // Select OPL chip 0
WhichChip = 0;
continue;
}
else if (reg == 3)
{ // Select OPL chip 1
WhichChip = 1;
continue;
}
else
{
data = *score++;
}
if (WhichChip == 0 || TwoChips)
{
io->OPLwriteReg(WhichChip, reg, data);
}
}
}
else if (RawPlayer == IMF)
{
WORD delay = 0;
@ -398,161 +446,33 @@ ADD_STAT (opl)
}
}
struct DiskWriterIO : public OPLio
OPLmusicFile::OPLmusicFile(const OPLmusicFile *source, const char *filename)
{
DiskWriterIO () : File(NULL) {}
virtual ~DiskWriterIO () { if (File != NULL) fclose (File); }
int OPLinit(const char *filename);
virtual void OPLwriteReg(int which, uint reg, uchar data);
FILE *File;
bool RawFormat;
};
class OPLmusicWriter : public musicBlock
{
public:
OPLmusicWriter (const char *songname, const char *filename);
~OPLmusicWriter ();
void Go ();
bool SharingData;
FILE *File;
};
OPLmusicWriter::OPLmusicWriter (const char *songname, const char *filename)
{
io = NULL;
SharingData = true;
if (songname == NULL)
ScoreLen = source->ScoreLen;
scoredata = new BYTE[ScoreLen];
memcpy(scoredata, source->scoredata, ScoreLen);
SamplesPerTick = source->SamplesPerTick;
RawPlayer = source->RawPlayer;
score = source->score;
TwoChips = source->TwoChips;
WhichChip = 0;
if (io != NULL)
{
if (BlockForStats == NULL)
{
Printf ("Not currently playing an OPL song.\n");
return;
}
scoredata = BlockForStats->scoredata;
OPLinstruments = BlockForStats->OPLinstruments;
}
else
{
SharingData = false;
int lumpnum = Wads.CheckNumForName (songname, ns_music);
if (lumpnum == -1)
{
Printf ("Song %s is unknown.\n", songname);
return;
}
FWadLump song = Wads.OpenLumpNum (lumpnum);
scoredata = new BYTE [song.GetLength ()];
song.Read (scoredata, song.GetLength());
FWadLump genmidi = Wads.OpenLumpName ("GENMIDI");
OPLloadBank (genmidi);
}
io = new DiskWriterIO ();
if (((DiskWriterIO *)io)->OPLinit (filename) == 0)
{
OPLplayMusic (127);
score = scoredata + ((MUSheader *)scoredata)->scoreStart;
Go ();
delete io;
}
io = new DiskWriterIO(filename);
io->OPLinit(TwoChips);
Restart();
}
OPLmusicWriter::~OPLmusicWriter ()
void OPLmusicFile::Dump()
{
if (io != NULL) delete io;
if (!SharingData)
int time;
time = PlayTick();
while (time != 0)
{
delete scoredata;
}
else
{
OPLinstruments = NULL;
io->WriteDelay(time);
time = PlayTick();
}
}
void OPLmusicWriter::Go ()
{
int next;
while ((next = playTick()) != 0)
{
MLtime += next;
while (next > 255)
{
io->OPLwriteReg (10, 0, 255);
next -= 255;
}
io->OPLwriteReg (10, 0, next);
}
io->OPLwriteReg (10, 0xFF, 0xFF);
}
int DiskWriterIO::OPLinit (const char *filename)
{
int numchips;
//size_t namelen;
// If the file extension is unknown or not present, the default format
// is RAW. Otherwise, you can use DRO. But not right now. The DRO format
// is still in a state of flux, so I don't want the hassle.
//namelen = strlen (filename);
RawFormat = 1; //(namelen < 5 || stricmp (filename + namelen - 4, ".dro") != 0);
File = fopen (filename, "wb");
if (File == NULL)
{
return -1;
}
if (RawFormat)
{
fwrite ("RAWADATA", 1, 8, File);
WORD clock = LittleShort(17045/2);
fwrite (&clock, 2, 1, File);
numchips = 1;
}
else
{
numchips = 2;
}
OPLchannels = OPL2CHANNELS*numchips;
for (int i = 0; i < numchips; ++i)
{
OPLwriteReg (i, 0x01, 0x20); // enable Waveform Select
OPLwriteReg (i, 0x0B, 0x40); // turn off CSW mode
OPLwriteReg (i, 0xBD, 0x00); // set vibrato/tremolo depth to low, set melodic mode
}
OPLshutup();
return 0;
}
void DiskWriterIO::OPLwriteReg(int which, uint reg, uchar data)
{
if (which == 10 || (reg != 0 && reg != 2 && reg != 0xFF))
{
struct { BYTE data, reg; } out = { data, reg };
fwrite (&out, 2, 1, File);
}
else
{
reg = reg;
}
}
CCMD (writeopl)
{
if (argv.argc() == 2)
{
OPLmusicWriter writer (NULL, argv[1]);
}
else if (argv.argc() == 3)
{
OPLmusicWriter writer (argv[1], argv[2]);
}
else
{
Printf ("Usage: writeopl [songname] <filename>");
}
}

View file

@ -9,8 +9,6 @@
#include "muslib.h"
#include "files.h"
#define OPL_SAMPLE_RATE 49716.0
class OPLmusicBlock : public musicBlock
{
public:
@ -43,16 +41,20 @@ protected:
class OPLmusicFile : public OPLmusicBlock
{
public:
OPLmusicFile(FILE *file, char *musiccache, int len, int maxSamples);
OPLmusicFile(FILE *file, char *musiccache, int len);
OPLmusicFile(const OPLmusicFile *source, const char *filename);
~OPLmusicFile();
bool IsValid() const;
void SetLooping(bool loop);
void Restart();
void Dump();
protected:
OPLmusicFile() {}
int PlayTick();
enum { NotRaw, RDosPlay, IMF } RawPlayer;
enum { RDosPlay, IMF, DosBox } RawPlayer;
int ScoreLen;
int WhichChip;
};

View file

@ -147,6 +147,11 @@ FString MusInfo::GetStats()
return "No stats available for this song";
}
MusInfo *MusInfo::GetOPLDumper(const char *filename)
{
return NULL;
}
void I_InitMusic (void)
{
static bool setatterm = false;
@ -304,7 +309,6 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
/* MUS are played as:
- OPL:
- if explicitly selected by $mididevice
- when opl_enable is true and no midi device is set for the song
- when snd_mididevice is -3 and no midi device is set for the song
Timidity:
@ -321,9 +325,9 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
- when snd_mididevice is >= 0 and no midi device is set for the song
- as fallback when both OPL and Timidity failed and snd_mididevice is >= 0
*/
if (((opl_enable || snd_mididevice == -3) && device == MDEV_DEFAULT) || device == MDEV_OPL)
if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL)
{
info = new OPLMUSSong (file, musiccache, len);
info = new MUSSong2 (file, musiccache, len, true);
}
else if (device == MDEV_TIMIDITY || (device == MDEV_DEFAULT && snd_mididevice == -2))
{
@ -369,7 +373,7 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
#ifdef _WIN32
if (info == NULL)
{
info = new MUSSong2 (file, musiccache, len);
info = new MUSSong2 (file, musiccache, len, false);
}
#endif // _WIN32
}
@ -384,7 +388,6 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
/* MIDI are played as:
OPL:
- if explicitly selected by $mididevice
- when opl_enable is true and no midi device is set for the song
- when snd_mididevice is -3 and no midi device is set for the song
Timidity:
@ -422,8 +425,11 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
}
#endif // _WIN32
}
// Check for RDosPlay raw OPL format
else if (id == MAKE_ID('R','A','W','A') && len >= 12)
// Check for various raw OPL formats
else if (len >= 12 &&
(id == MAKE_ID('R','A','W','A') || // Rdos Raw OPL
id == MAKE_ID('D','B','R','A') || // DosBox Raw OPL
id == MAKE_ID('A','D','L','I'))) // Martin Fernandez's modified IMF
{
DWORD fullsig[2];
@ -441,30 +447,9 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
memcpy(fullsig, musiccache, 8);
}
if (fullsig[1] == MAKE_ID('D','A','T','A'))
{
info = new OPLMUSSong (file, musiccache, len);
}
}
// Check for Martin Fernandez's modified IMF format
else if (id == MAKE_ID('A','D','L','I'))
{
char fullhead[6];
if (file != NULL)
{
if (fread (fullhead, 1, 6, file) != 6)
{
fclose (file);
return 0;
}
fseek (file, -6, SEEK_CUR);
}
else
{
memcpy(fullhead, musiccache, 6);
}
if (fullhead[4] == 'B' && fullhead[5] == 1)
if ((fullsig[0] == MAKE_ID('R','A','W','A') && fullsig[1] == MAKE_ID('D','A','T','A')) ||
(fullsig[0] == MAKE_ID('D','B','R','A') && fullsig[1] == MAKE_ID('W','O','P','L')) ||
(fullsig[0] == MAKE_ID('A','D','L','I') && (fullsig[1] & MAKE_ID(255,255,0,0)) == MAKE_ID('B',1,0,0)))
{
info = new OPLMUSSong (file, musiccache, len);
}
@ -597,3 +582,40 @@ ADD_STAT(music)
}
return "No song playing";
}
//==========================================================================
//
// CCMD writeopl
//
// If the current song can be played with OPL instruments, dump it to
// the specified file on disk.
//
//==========================================================================
CCMD (writeopl)
{
if (argv.argc() == 2)
{
if (currSong == NULL)
{
Printf ("No song is currently playing.\n");
}
else
{
MusInfo *dumper = currSong->GetOPLDumper(argv[1]);
if (dumper == NULL)
{
Printf ("Current song cannot be saved as OPL data.\n");
}
else
{
dumper->Play(false);
delete dumper;
}
}
}
else
{
Printf ("Usage: writeopl <filename>");
}
}

View file

@ -45,6 +45,7 @@ public:
virtual bool SetPosition (int order);
virtual void Update();
virtual FString GetStats();
virtual MusInfo *GetOPLDumper(const char *filename);
enum EState
{
@ -152,7 +153,7 @@ protected:
// OPL implementation of a MIDI output device -------------------------------
class OPLMIDIDevice : public MIDIDevice, OPLmusicBlock
class OPLMIDIDevice : public MIDIDevice, protected OPLmusicBlock
{
public:
OPLMIDIDevice();
@ -191,6 +192,17 @@ protected:
DWORD Position;
};
// OPL dumper implementation of a MIDI output device ------------------------
class OPLDumperMIDIDevice : public OPLMIDIDevice
{
public:
OPLDumperMIDIDevice(const char *filename);
~OPLDumperMIDIDevice();
int Resume();
void Stop();
};
// Base class for streaming MUS and MIDI files ------------------------------
class MIDIStreamer : public MusInfo
@ -210,13 +222,15 @@ public:
void Update();
protected:
static void Callback(unsigned int uMsg, void *userdata, DWORD dwParam1, DWORD dwParam2);
MIDIStreamer(const char *dumpname);
void OutputVolume (DWORD volume);
int FillBuffer(int buffer_num, int max_events, DWORD max_time);
bool ServiceEvent();
int VolumeControllerChange(int channel, int volume);
static void Callback(unsigned int uMsg, void *userdata, DWORD dwParam1, DWORD dwParam2);
// Virtuals for subclasses to override
virtual void CheckCaps();
virtual void DoInitialSetup() = 0;
@ -261,6 +275,7 @@ protected:
DWORD Volume;
bool UseOPLDevice;
bool CallbackIsThreaded;
FString DumpFilename;
};
// MUS file played with a MIDI stream ---------------------------------------
@ -268,10 +283,14 @@ protected:
class MUSSong2 : public MIDIStreamer
{
public:
MUSSong2 (FILE *file, char *musiccache, int length);
~MUSSong2 ();
MUSSong2(FILE *file, char *musiccache, int length, bool opl);
~MUSSong2();
MusInfo *GetOPLDumper(const char *filename);
protected:
MUSSong2(const MUSSong2 *original, const char *filename); //OPL dump constructor
void DoInitialSetup();
void DoRestart();
bool CheckDone();
@ -288,10 +307,14 @@ protected:
class MIDISong2 : public MIDIStreamer
{
public:
MIDISong2 (FILE *file, char *musiccache, int length, bool opl);
~MIDISong2 ();
MIDISong2(FILE *file, char *musiccache, int length, bool opl);
~MIDISong2();
MusInfo *GetOPLDumper(const char *filename);
protected:
MIDISong2(const MIDISong2 *original, const char *filename); // OPL dump constructor
void CheckCaps();
void DoInitialSetup();
void DoRestart();
@ -307,6 +330,7 @@ protected:
void SetTempo(int new_tempo);
BYTE *MusHeader;
int SongLen;
TrackInfo *Tracks;
TrackInfo *TrackDue;
int NumTracks;
@ -381,19 +405,29 @@ protected:
class OPLMUSSong : public StreamSong
{
public:
OPLMUSSong (FILE *file, char * musiccache, int length);
OPLMUSSong (FILE *file, char *musiccache, int length);
~OPLMUSSong ();
void Play (bool looping);
bool IsPlaying ();
bool IsValid () const;
void ResetChips ();
MusInfo *GetOPLDumper(const char *filename);
protected:
OPLMUSSong(const OPLMUSSong *original, const char *filename); // OPL dump constructor
static bool FillStream (SoundStream *stream, void *buff, int len, void *userdata);
OPLmusicFile *Music;
};
class OPLMUSDumper : public OPLMUSSong
{
public:
OPLMUSDumper(const OPLMUSSong *original, const char *filename);
void Play(bool looping);
};
// CD track/disk played through the multimedia system -----------------------
class CDSong : public MusInfo
@ -430,4 +464,3 @@ extern MusInfo *currSong;
extern int nomusic;
EXTERN_CVAR (Float, snd_musicvolume)
EXTERN_CVAR (Bool, opl_enable)

View file

@ -44,8 +44,6 @@
// MACROS ------------------------------------------------------------------
#define MAX_TIME (1000000/20) // Send out 1/20 of a sec of events at a time.
// Used by SendCommand to check for unexpected end-of-track conditions.
#define CHECK_FINISHED \
if (track->TrackP >= track->MaxTrackP) \
@ -115,6 +113,7 @@ MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len, bool opl)
}
#endif
MusHeader = new BYTE[len];
SongLen = len;
if (file != NULL)
{
if (fread(MusHeader, 1, len, file) != (size_t)len)
@ -754,3 +753,43 @@ void MIDISong2::SetTempo(int new_tempo)
Tempo = new_tempo;
}
}
//==========================================================================
//
// MIDISong2 :: GetOPLDumper
//
//==========================================================================
MusInfo *MIDISong2::GetOPLDumper(const char *filename)
{
return new MIDISong2(this, filename);
}
//==========================================================================
//
// MIDISong2 OPL Dumping Constructor
//
//==========================================================================
MIDISong2::MIDISong2(const MIDISong2 *original, const char *filename)
: MIDIStreamer(filename)
{
SongLen = original->SongLen;
MusHeader = new BYTE[original->SongLen];
memcpy(MusHeader, original->MusHeader, original->SongLen);
Format = original->Format;
NumTracks = original->NumTracks;
DesignationMask = 0;
Division = original->Division;
Tempo = InitialTempo = original->InitialTempo;
Tracks = new TrackInfo[NumTracks];
for (int i = 0; i < NumTracks; ++i)
{
TrackInfo *newtrack = &Tracks[i];
const TrackInfo *oldtrack = &original->Tracks[i];
newtrack->TrackBegin = MusHeader + (oldtrack->TrackBegin - original->MusHeader);
newtrack->TrackP = 0;
newtrack->MaxTrackP = oldtrack->MaxTrackP;
}
}

View file

@ -91,6 +91,25 @@ MIDIStreamer::MIDIStreamer(bool opl)
#endif
}
//==========================================================================
//
// MIDIStreamer OPL Dumping Constructor
//
//==========================================================================
MIDIStreamer::MIDIStreamer(const char *dumpname)
: MIDI(0), DumpFilename(dumpname),
#ifdef _WIN32
PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
#endif
Division(0), InitialTempo(500000), UseOPLDevice(true)
{
#ifdef _WIN32
BufferDoneEvent = NULL;
ExitEvent = NULL;
#endif
}
//==========================================================================
//
// MIDIStreamer Destructor
@ -163,7 +182,7 @@ void MIDIStreamer::CheckCaps()
//
//==========================================================================
void MIDIStreamer::Play (bool looping)
void MIDIStreamer::Play(bool looping)
{
DWORD tid;
@ -175,6 +194,11 @@ void MIDIStreamer::Play (bool looping)
InitialPlayback = true;
assert(MIDI == NULL);
if (DumpFilename.IsNotEmpty())
{
MIDI = new OPLDumperMIDIDevice(DumpFilename);
}
else
#ifdef _WIN32
if (!UseOPLDevice)
{
@ -188,7 +212,7 @@ void MIDIStreamer::Play (bool looping)
#ifndef _WIN32
assert(MIDI->NeedThreadedCallback() == false);
#endif
#endif
if (0 != MIDI->Open(Callback, this))
{
@ -281,7 +305,7 @@ void MIDIStreamer::Play (bool looping)
//
//==========================================================================
void MIDIStreamer::Pause ()
void MIDIStreamer::Pause()
{
if (m_Status == STATE_Playing)
{
@ -302,7 +326,7 @@ void MIDIStreamer::Pause ()
//
//==========================================================================
void MIDIStreamer::Resume ()
void MIDIStreamer::Resume()
{
if (m_Status == STATE_Paused)
{
@ -322,7 +346,7 @@ void MIDIStreamer::Resume ()
//
//==========================================================================
void MIDIStreamer::Stop ()
void MIDIStreamer::Stop()
{
EndQueued = 2;
#ifdef _WIN32
@ -355,7 +379,7 @@ void MIDIStreamer::Stop ()
//
//==========================================================================
bool MIDIStreamer::IsPlaying ()
bool MIDIStreamer::IsPlaying()
{
return m_Status != STATE_Stopped;
}
@ -369,7 +393,7 @@ bool MIDIStreamer::IsPlaying ()
//
//==========================================================================
void MIDIStreamer::MusicVolumeChanged ()
void MIDIStreamer::MusicVolumeChanged()
{
if (MIDI->FakeVolume())
{
@ -676,7 +700,7 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time)
//==========================================================================
//
// MIDIDevice constructor and desctructor stubs.
// MIDIDevice stubs.
//
//==========================================================================

View file

@ -31,8 +31,6 @@
**---------------------------------------------------------------------------
*/
#ifdef _WIN32
// HEADER FILES ------------------------------------------------------------
#include "i_musicinterns.h"
@ -88,8 +86,8 @@ static const BYTE CtrlTranslate[15] =
//
//==========================================================================
MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len)
: MIDIStreamer(false), MusHeader(0), MusBuffer(0)
MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len, bool opl)
: MIDIStreamer(opl), MusHeader(0), MusBuffer(0)
{
if (ExitEvent == NULL)
{
@ -304,4 +302,32 @@ end:
}
return events;
}
#endif
//==========================================================================
//
// MUSSong2 :: GetOPLDumper
//
//==========================================================================
MusInfo *MUSSong2::GetOPLDumper(const char *filename)
{
return new MUSSong2(this, filename);
}
//==========================================================================
//
// MUSSong2 OPL Dumping Constructor
//
//==========================================================================
MUSSong2::MUSSong2(const MUSSong2 *original, const char *filename)
: MIDIStreamer(filename)
{
int songstart = LittleShort(original->MusHeader->SongStart);
MaxMusP = original->MaxMusP;
MusHeader = (MUSHeader *)new BYTE[songstart + MaxMusP];
memcpy(MusHeader, original->MusHeader, songstart + MaxMusP);
MusBuffer = (BYTE *)MusHeader + songstart;
Division = 140;
InitialTempo = 1000000;
}

View file

@ -1,7 +1,5 @@
#include "i_musicinterns.h"
CVAR (Bool, opl_enable, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
static bool OPL_Active;
CUSTOM_CVAR (Bool, opl_onechip, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
@ -12,12 +10,11 @@ CUSTOM_CVAR (Bool, opl_onechip, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
}
}
OPLMUSSong::OPLMUSSong (FILE *file, char *musiccache, int len)
{
int samples = int(OPL_SAMPLE_RATE / 14);
Music = new OPLmusicFile (file, musiccache, len, samples);
Music = new OPLmusicFile (file, musiccache, len);
m_Stream = GSnd->CreateStream (FillStream, samples*4,
SoundStream::Mono | SoundStream::Float, int(OPL_SAMPLE_RATE), this);
@ -63,7 +60,7 @@ void OPLMUSSong::Play (bool looping)
Music->SetLooping (looping);
Music->Restart ();
if (m_Stream->Play (true, snd_musicvolume, false))
if (m_Stream == NULL || m_Stream->Play (true, snd_musicvolume, false))
{
m_Status = STATE_Playing;
}
@ -74,3 +71,24 @@ bool OPLMUSSong::FillStream (SoundStream *stream, void *buff, int len, void *use
OPLMUSSong *song = (OPLMUSSong *)userdata;
return song->Music->ServiceStream (buff, len);
}
MusInfo *OPLMUSSong::GetOPLDumper(const char *filename)
{
return new OPLMUSDumper(this, filename);
}
OPLMUSSong::OPLMUSSong(const OPLMUSSong *original, const char *filename)
{
Music = new OPLmusicFile(original->Music, filename);
m_Stream = NULL;
}
OPLMUSDumper::OPLMUSDumper(const OPLMUSSong *original, const char *filename)
: OPLMUSSong(original, filename)
{
}
void OPLMUSDumper::Play(bool looping)
{
Music->Dump();
}

View file

@ -2844,6 +2844,10 @@
RelativePath=".\src\oplsynth\music_opl_mididevice.cpp"
>
</File>
<File
RelativePath=".\src\oplsynth\music_opldumper_mididevice.cpp"
>
</File>
<File
RelativePath="src\sound\music_spc.cpp"
>
@ -2887,10 +2891,6 @@
RelativePath=".\src\oplsynth\fmopl.h"
>
</File>
<File
RelativePath=".\src\oplsynth\mlkernel.cpp"
>
</File>
<File
RelativePath=".\src\oplsynth\mlopl.cpp"
>