From 3a5afd141834abb79069f5e86061b298f8977f10 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 3 Apr 2008 02:31:39 +0000 Subject: [PATCH] - 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) --- docs/rh-log.txt | 23 +- src/m_options.cpp | 1 - src/oplsynth/fmopl.cpp | 11 - src/oplsynth/fmopl.h | 11 +- src/oplsynth/mlkernel.cpp | 124 ------- src/oplsynth/mlopl_io.cpp | 136 ++++--- src/oplsynth/music_opl_mididevice.cpp | 4 +- src/oplsynth/music_opldumper_mididevice.cpp | 372 ++++++++++++++++++++ src/oplsynth/muslib.h | 40 ++- src/oplsynth/opl_mus_player.cpp | 282 ++++++--------- src/oplsynth/opl_mus_player.h | 10 +- src/sound/i_music.cpp | 84 +++-- src/sound/i_musicinterns.h | 49 ++- src/sound/music_midi_midiout.cpp | 43 ++- src/sound/music_midistream.cpp | 40 ++- src/sound/music_mus_midiout.cpp | 36 +- src/sound/music_mus_opl.cpp | 28 +- zdoom.vcproj | 8 +- 18 files changed, 846 insertions(+), 456 deletions(-) delete mode 100644 src/oplsynth/mlkernel.cpp create mode 100644 src/oplsynth/music_opldumper_mididevice.cpp 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" > - -