diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 373e262b9..0dc335988 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -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] 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 diff --git a/src/m_options.cpp b/src/m_options.cpp index 6b0ae1348..270735060 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -1266,7 +1266,6 @@ static menu_t SoundMenu = * *=======================================*/ -EXTERN_CVAR (Bool, opl_enable) EXTERN_CVAR (Bool, opl_onechip) static menuitem_t AdvSoundItems[] = diff --git a/src/oplsynth/fmopl.cpp b/src/oplsynth/fmopl.cpp index 7928a3134..f367e81c9 100644 --- a/src/oplsynth/fmopl.cpp +++ b/src/oplsynth/fmopl.cpp @@ -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) */ diff --git a/src/oplsynth/fmopl.h b/src/oplsynth/fmopl.h index 464026519..b79042df1 100644 --- a/src/oplsynth/fmopl.h +++ b/src/oplsynth/fmopl.h @@ -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); diff --git a/src/oplsynth/mlkernel.cpp b/src/oplsynth/mlkernel.cpp deleted file mode 100644 index d553e7b85..000000000 --- a/src/oplsynth/mlkernel.cpp +++ /dev/null @@ -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; -} diff --git a/src/oplsynth/mlopl_io.cpp b/src/oplsynth/mlopl_io.cpp index 6eb62aec3..4f197a691 100644 --- a/src/oplsynth/mlopl_io.cpp +++ b/src/oplsynth/mlopl_io.cpp @@ -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 */ diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp index 0064d0a74..10b8ff00b 100644 --- a/src/oplsynth/music_opl_mididevice.cpp +++ b/src/oplsynth/music_opl_mididevice.cpp @@ -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); } //========================================================================== diff --git a/src/oplsynth/music_opldumper_mididevice.cpp b/src/oplsynth/music_opldumper_mididevice.cpp new file mode 100644 index 000000000..39ff5717d --- /dev/null +++ b/src/oplsynth/music_opldumper_mididevice.cpp @@ -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); + } + } +} diff --git a/src/oplsynth/muslib.h b/src/oplsynth/muslib.h index 7128ccdc8..ad9bbf6f5 100644 --- a/src/oplsynth/muslib.h +++ b/src/oplsynth/muslib.h @@ -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_ diff --git a/src/oplsynth/opl_mus_player.cpp b/src/oplsynth/opl_mus_player.cpp index 22ad6cc9a..f8201e035 100644 --- a/src/oplsynth/opl_mus_player.cpp +++ b/src/oplsynth/opl_mus_player.cpp @@ -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(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] "); - } - -} diff --git a/src/oplsynth/opl_mus_player.h b/src/oplsynth/opl_mus_player.h index c82b6e952..0379413d9 100644 --- a/src/oplsynth/opl_mus_player.h +++ b/src/oplsynth/opl_mus_player.h @@ -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; }; diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 4fcdc57f0..6a148f33c 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -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 "); + } +} diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 646e20cd0..58e31d6e8 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -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) diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_midi_midiout.cpp index a25ff2951..99b918f5b 100644 --- a/src/sound/music_midi_midiout.cpp +++ b/src/sound/music_midi_midiout.cpp @@ -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; + } +} diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index b71df56e4..4378f4495 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -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. // //========================================================================== diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp index d2ee0dd81..144f0eb92 100644 --- a/src/sound/music_mus_midiout.cpp +++ b/src/sound/music_mus_midiout.cpp @@ -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; +} diff --git a/src/sound/music_mus_opl.cpp b/src/sound/music_mus_opl.cpp index f6948f93d..52f8d68ed 100644 --- a/src/sound/music_mus_opl.cpp +++ b/src/sound/music_mus_opl.cpp @@ -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(); +} diff --git a/zdoom.vcproj b/zdoom.vcproj index 0337faee4..ce49f3b04 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -2844,6 +2844,10 @@ RelativePath=".\src\oplsynth\music_opl_mididevice.cpp" > + + @@ -2887,10 +2891,6 @@ RelativePath=".\src\oplsynth\fmopl.h" > - -