Index: build/premake/ext-rtmidi.lua =================================================================== --- build/premake/ext-rtmidi.lua (nonexistent) +++ build/premake/ext-rtmidi.lua (working copy) @@ -0,0 +1,26 @@ + + project "rtmidi" + uuid "05BBD03D-0985-4D76-8DDD-534DA631C3A8" + language "C++" + location ( "../../build/" .. mpt_projectpathname .. "/ext" ) + mpt_projectname = "rtmidi" + dofile "../../build/premake/premake-defaults-LIBorDLL.lua" + dofile "../../build/premake/premake-defaults.lua" + dofile "../../build/premake/premake-defaults-winver.lua" + targetname "openmpt-rtmidi" + filter {} + filter { "action:vs*" } + characterset "Unicode" + filter {} + files { + "../../include/rtmidi/RtMidi.cpp" + } + files { + "../../include/rtmidi/RtMidi.h" + } + defines { + "__WINDOWS_MM__" + } + links { + "winmm" + } Property changes on: build/premake/ext-rtmidi.lua ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/x-lua \ No newline at end of property Index: build/premake/mpt-OpenMPT-VSTi.lua =================================================================== --- build/premake/mpt-OpenMPT-VSTi.lua (revision 7555) +++ build/premake/mpt-OpenMPT-VSTi.lua (working copy) @@ -87,8 +87,8 @@ "opus", "opusfile", "portaudio", - "portmidi", "r8brain", + "rtmidi", "soundtouch", "vorbis", } Index: build/premake/mpt-OpenMPT.lua =================================================================== --- build/premake/mpt-OpenMPT.lua (revision 7555) +++ build/premake/mpt-OpenMPT.lua (working copy) @@ -122,8 +122,8 @@ "opus", "opusfile", "portaudio", - "portmidi", "r8brain", + "rtmidi", "soundtouch", "vorbis", } Index: build/premake/premake.lua =================================================================== --- build/premake/premake.lua (revision 7555) +++ build/premake/premake.lua (working copy) @@ -376,8 +376,8 @@ dofile "../../build/premake/ext-opus.lua" dofile "../../build/premake/ext-opusfile.lua" dofile "../../build/premake/ext-portaudio.lua" - dofile "../../build/premake/ext-portmidi.lua" dofile "../../build/premake/ext-r8brain.lua" + dofile "../../build/premake/ext-rtmidi.lua" dofile "../../build/premake/ext-smbPitchShift.lua" dofile "../../build/premake/ext-soundtouch.lua" dofile "../../build/premake/ext-UnRAR.lua" @@ -405,8 +405,8 @@ dofile "../../build/premake/ext-opus.lua" dofile "../../build/premake/ext-opusfile.lua" dofile "../../build/premake/ext-portaudio.lua" - dofile "../../build/premake/ext-portmidi.lua" dofile "../../build/premake/ext-r8brain.lua" + dofile "../../build/premake/ext-rtmidi.lua" dofile "../../build/premake/ext-smbPitchShift.lua" dofile "../../build/premake/ext-soundtouch.lua" dofile "../../build/premake/ext-UnRAR.lua" @@ -429,8 +429,8 @@ dofile "../../build/premake/ext-opus.lua" dofile "../../build/premake/ext-opusfile.lua" dofile "../../build/premake/ext-portaudio.lua" - dofile "../../build/premake/ext-portmidi.lua" dofile "../../build/premake/ext-r8brain.lua" + dofile "../../build/premake/ext-rtmidi.lua" dofile "../../build/premake/ext-smbPitchShift.lua" dofile "../../build/premake/ext-soundtouch.lua" dofile "../../build/premake/ext-UnRAR.lua" @@ -455,8 +455,8 @@ dofile "../../build/premake/ext-opus.lua" dofile "../../build/premake/ext-opusfile.lua" dofile "../../build/premake/ext-portaudio.lua" - dofile "../../build/premake/ext-portmidi.lua" dofile "../../build/premake/ext-r8brain.lua" + dofile "../../build/premake/ext-rtmidi.lua" dofile "../../build/premake/ext-smbPitchShift.lua" dofile "../../build/premake/ext-soundtouch.lua" dofile "../../build/premake/ext-UnRAR.lua" @@ -481,8 +481,8 @@ dofile "../../build/premake/ext-opus.lua" dofile "../../build/premake/ext-opusfile.lua" dofile "../../build/premake/ext-portaudio.lua" - dofile "../../build/premake/ext-portmidi.lua" dofile "../../build/premake/ext-pugixml.lua" + dofile "../../build/premake/ext-rtmidi.lua" dofile "../../build/premake/ext-r8brain.lua" dofile "../../build/premake/ext-smbPitchShift.lua" dofile "../../build/premake/ext-soundtouch.lua" Index: plugins/MidiInOut/MidiInOut.cpp =================================================================== --- plugins/MidiInOut/MidiInOut.cpp (revision 7555) +++ plugins/MidiInOut/MidiInOut.cpp (working copy) @@ -20,12 +20,16 @@ OPENMPT_NAMESPACE_BEGIN -int MidiInOut::numInstances = 0; - IMixPlugin* MidiInOut::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct) //------------------------------------------------------------------------------------------------ { - return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct); + try + { + return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct); + } catch(RtMidiError &) + { + return nullptr; + } } @@ -33,14 +37,10 @@ : IMidiPlugin(factory, sndFile, mixStruct) , latencyCompensation(true) , programName(_T("Default")) + , inputDevice(midiIn) + , outputDevice(midiOut) //--------------------------------------------------------------------------------------- { - if(!numInstances++) - { - Pt_Start(1, nullptr, nullptr); - Pm_Initialize(); - } - m_mixBuffer.Initialize(2, 2); InsertIntoFactoryList(); } @@ -50,13 +50,6 @@ //--------------------- { Suspend(); - - if(--numInstances == 0) - { - // This terminates MIDI output for all instances of the plugin, so only ever do it if this was the only instance left. - Pm_Terminate(); - Pt_Stop(); - } } @@ -127,9 +120,9 @@ return; uint32 nameStrSize = file.ReadUint32LE(); - PmDeviceID inID = file.ReadUint32LE(); + MidiDevice::ID inID = file.ReadUint32LE(); uint32 inStrSize = file.ReadUint32LE(); - PmDeviceID outID = file.ReadUint32LE(); + MidiDevice::ID outID = file.ReadUint32LE(); uint32 outStrSize = file.ReadUint32LE(); latencyCompensation = file.ReadUint32LE() != 0; @@ -139,16 +132,21 @@ file.ReadString(s, inStrSize); s = mpt::ToCharset(mpt::CharsetLocale, mpt::CharsetUTF8, s); - if(s != GetDeviceName(inID) || !IsInputDevice(inID)) + if(s != inputDevice.GetPortName(inID)) { // Stored name differs from actual device name - try finding another device with the same name. - const PmDeviceInfo *device; - for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++) + unsigned int ports = midiIn.getPortCount(); + for(unsigned int i = 0; i < ports; i++) { - if(device->input && s == device->name) + try { - inID = i; - break; + if(s == inputDevice.GetPortName(i)) + { + inID = i; + break; + } + } catch(RtMidiError &) + { } } } @@ -155,16 +153,21 @@ file.ReadString(s, outStrSize); s = mpt::ToCharset(mpt::CharsetLocale, mpt::CharsetUTF8, s); - if(s != GetDeviceName(outID) || !IsOutputDevice(outID)) + if(s != outputDevice.GetPortName(outID)) { // Stored name differs from actual device name - try finding another device with the same name. - const PmDeviceInfo *device; - for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++) + unsigned int ports = midiOut.getPortCount(); + for(unsigned int i = 0; i < ports; i++) { - if(device->output && s == device->name) + try { - outID = i; - break; + if(s == outputDevice.GetPortName(i)) + { + outID = i; + break; + } + } catch(RtMidiError &) + { } } } @@ -177,7 +180,7 @@ void MidiInOut::SetParameter(PlugParamIndex index, PlugParamValue value) //---------------------------------------------------------------------- { - PmDeviceID newDevice = ParameterToDeviceID(value); + MidiDevice::ID newDevice = ParameterToDeviceID(value); OpenDevice(newDevice, (index == kInputParameter)); // Update selection in editor @@ -238,12 +241,13 @@ { MPT_LOCK_GUARD lock(mutex); - if(outputDevice.stream != nullptr) + if(midiOut.isPortOpen()) { // Send MIDI clock if(nextClock < 1) { - Pm_WriteShort(outputDevice.stream, Now(), 0xF8); + std::vector message(1, 0xF8); + midiOut.sendMessage(&message); double bpm = m_SndFile.GetCurrentBPM(); if(bpm != 0.0) { @@ -254,26 +258,22 @@ } // We don't do any audio processing here, but we process incoming MIDI events. - if(inputDevice.stream == nullptr) + if(!midiOut.isPortOpen()) return; - while(Pm_Poll(inputDevice.stream)) + std::vector message; + while(midiIn.getMessage(&message), !message.empty()) { // Read incoming MIDI events. - PmEvent buffer; - Pm_Read(inputDevice.stream, &buffer, 1); // Discard events if bypassed if(IsBypassed()) continue; - mpt::byte message[sizeof(buffer.message)]; - memcpy(message, &buffer.message, sizeof(message)); - if(!bufferedMessage.empty()) { - bufferedMessage.push_back(buffer.message); - if(buffer.message & 0x80808080) + bufferedMessage.insert(bufferedMessage.end(), message.begin(), message.end()); + if(message.back() == 0xF7) { // End of message found! ReceiveSysex(bufferedMessage.data(), bufferedMessage.size() * sizeof(bufferedMessage[0])); @@ -280,17 +280,19 @@ bufferedMessage.clear(); } continue; - } else if(message[0] == 0xF0) + } else if(message.front() == 0xF0) { // Start of SysEx message... - if(message[1] != 0xF7 && message[2] != 0xF7 && message[3] != 0xF7) - bufferedMessage.push_back(buffer.message); // ...but not the end! + if(message.back() != 0xF7) + bufferedMessage.insert(bufferedMessage.end(), message.begin(), message.end()); // ...but not the end! else - ReceiveSysex(message, sizeof(message)); + ReceiveSysex(&message[0], message.size() * sizeof(message[0])); continue; } - ReceiveMidi(buffer.message); + uint32 msg = 0; + memcpy(&msg, &message[0], std::min(message.size(), sizeof(msg))); + ReceiveMidi(msg); } } @@ -304,9 +306,10 @@ nextClock = 0; OpenDevice(inputDevice.index, true); OpenDevice(outputDevice.index, false); - if(outputDevice.stream != nullptr) + if(midiOut.isPortOpen()) { - Pm_WriteShort(outputDevice.stream, Now(), 0xFA); // Start + std::vector message(1, 0xFA); // Start + midiOut.sendMessage(&message); } } @@ -316,9 +319,10 @@ //----------------------- { // Suspend MIDI I/O - if(outputDevice.stream != nullptr) + if(midiOut.isPortOpen()) { - Pm_WriteShort(outputDevice.stream, Now(), 0xFC); // Stop + std::vector message(1, 0xFC); // Stop + midiOut.sendMessage(&message); } CloseDevice(inputDevice); CloseDevice(outputDevice); @@ -330,11 +334,12 @@ void MidiInOut::PositionChanged() //------------------------------- { - if(outputDevice.stream != nullptr) + if(midiOut.isPortOpen()) { - const PtTimestamp now = Now(); - Pm_WriteShort(outputDevice.stream, now, 0xFC); // Stop - Pm_WriteShort(outputDevice.stream, now, 0xFA); // Start + std::vector message; + message.push_back(0xFC); // Stop + message.push_back(0xFA); // Start + midiOut.sendMessage(&message); } } @@ -343,27 +348,30 @@ bool MidiInOut::MidiSend(uint32 midiCode) //--------------------------------------- { - if(outputDevice.stream == nullptr || IsBypassed()) + if(!midiOut.isPortOpen() || IsBypassed()) { // We need an output device to send MIDI messages to. return true; } - Pm_WriteShort(outputDevice.stream, Now(), midiCode); + std::vector message(3, 0); + memcpy(&message[0], &midiCode, 3); + midiOut.sendMessage(&message); return true; } -bool MidiInOut::MidiSysexSend(const void *message, uint32 /*length*/) -//------------------------------------------------------------------- +bool MidiInOut::MidiSysexSend(const void *sysex, uint32 length) +//------------------------------------------------------------- { - if(outputDevice.stream == nullptr || IsBypassed()) + if(!midiOut.isPortOpen() || IsBypassed()) { // We need an output device to send MIDI messages to. return true; } - Pm_WriteSysEx(outputDevice.stream, Now(), const_cast(static_cast(message))); + std::vector message(static_cast(sysex), static_cast(sysex) + length); + midiOut.sendMessage(&message); return true; } @@ -405,20 +413,13 @@ } -static PmTimestamp PtTimeWrapper(void* /*time_info*/) -//--------------------------------------------------- -{ - return Pt_Time(); -} - - // Open a device for input or output. -void MidiInOut::OpenDevice(PmDeviceID newDevice, bool asInputDevice) -//------------------------------------------------------------------ +void MidiInOut::OpenDevice(MidiDevice::ID newDevice, bool asInputDevice) +//---------------------------------------------------------------------- { MidiDevice &device = asInputDevice ? inputDevice : outputDevice; - if(device.index == newDevice && device.stream != nullptr) + if(device.index == newDevice && device.stream.isPortOpen()) { // No need to re-open this device. return; @@ -427,15 +428,16 @@ CloseDevice(device); device.index = newDevice; + device.stream.closePort(); if(device.index == kNoDevice) { // Dummy device - device = MidiDevice(); + device.name = ""; return; } - PmError result = pmNoError; + device.name = device.GetPortName(newDevice); if(m_isResumed) { // Don't open MIDI devices if we're not processing. @@ -442,31 +444,20 @@ // This has to be done since we receive MIDI events in processReplacing(), // so if no processing is happening, some irrelevant events might be queued until the next processing happens... MPT_LOCK_GUARD lock(mutex); - if(asInputDevice) + + try { - result = Pm_OpenInput(&device.stream, newDevice, nullptr, 0, nullptr, nullptr); - } else + device.stream.openPort(newDevice); + } catch(RtMidiError &error) { - if(latencyCompensation) + device.name = "Unavailable"; + MidiInOutEditor *editor = dynamic_cast(GetEditor()); + if(editor != nullptr) { - // buffer of 10000 events - result = Pm_OpenOutput(&device.stream, newDevice, nullptr, 10000, PtTimeWrapper, nullptr, Util::Round(1000.0 * GetOutputLatency())); - } else - { - result = Pm_OpenOutput(&device.stream, newDevice, nullptr, 0, nullptr, nullptr, 0); + Reporting::Error("MIDI device cannot be opened:" + error.getMessage(), "MIDI Input / Output", editor); } } } - - // Update current device name - device.name = GetDeviceName(device.index); - - MidiInOutEditor *editor = dynamic_cast(GetEditor()); - if(result != pmNoError && editor != nullptr) - { - // Display a warning if the editor is open. - Reporting::Error("MIDI device cannot be opened!", "MIDI Input / Output", editor); - } } @@ -474,60 +465,26 @@ void MidiInOut::CloseDevice(MidiDevice &device) //--------------------------------------------- { - if(device.stream != nullptr) + if(device.stream.isPortOpen()) { MPT_LOCK_GUARD lock(mutex); - Pm_Close(device.stream); - device.stream = nullptr; + device.stream.closePort(); } } // Get a device name -const char *MidiInOut::GetDeviceName(PmDeviceID index) const -//----------------------------------------------------------- +std::string MidiDevice::GetPortName(MidiDevice::ID port) +//------------------------------------------------------ { - const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index); - if(deviceInfo != nullptr) - return deviceInfo->name; - else - return "Unavailable"; + std::string portName = stream.getPortName(port); +#if MPT_OS_WINDOWS + // Remove auto-appended port number + if(portName.length() >= 2) + return portName.substr(0, portName.find_last_of(' ')); +#endif + return portName; } -bool MidiInOut::IsInputDevice(PmDeviceID index) const -//---------------------------------------------------- -{ - if(index == kNoDevice) - return true; - - const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index); - if(deviceInfo != nullptr) - return deviceInfo->input != 0; - else - return false; -} - - -bool MidiInOut::IsOutputDevice(PmDeviceID index) const -//---------------------------------------------------- -{ - if(index == kNoDevice) - return true; - - const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index); - if(deviceInfo != nullptr) - return deviceInfo->output != 0; - else - return false; -} - - -PtTimestamp MidiInOut::Now() const -//-------------------------------- -{ - return (latencyCompensation ? Pt_Time() : 0); -} - - OPENMPT_NAMESPACE_END Index: plugins/MidiInOut/MidiInOut.h =================================================================== --- plugins/MidiInOut/MidiInOut.h (revision 7555) +++ plugins/MidiInOut/MidiInOut.h (working copy) @@ -12,8 +12,7 @@ #include "../../common/mptMutex.h" #include "../../soundlib/plugins/PlugInterface.h" -#include -#include +#include "../../include/rtmidi/RtMidi.h" OPENMPT_NAMESPACE_BEGIN @@ -24,16 +23,20 @@ //============== { public: - PmDeviceID index; - PortMidiStream *stream; + typedef unsigned int ID; + + RtMidi &stream; std::string name; + ID index; public: - MidiDevice() - : index(-1) // MidiInOut::kNoDevice - , stream(nullptr) + MidiDevice(RtMidi &stream) + : stream(stream) , name("") + , index(ID(-1)) // MidiInOut::kNoDevice { } + + std::string GetPortName(ID port); }; @@ -62,12 +65,13 @@ double nextClock; // I/O device settings + RtMidiIn midiIn; + RtMidiOut midiOut; MidiDevice inputDevice; MidiDevice outputDevice; bool latencyCompensation; CString programName; - static int numInstances; public: static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct); @@ -75,13 +79,13 @@ ~MidiInOut(); // Translate a VST parameter to a PortMidi device ID - static PmDeviceID ParameterToDeviceID(float value) + static MidiDevice::ID ParameterToDeviceID(float value) { - return static_cast(value * static_cast(kMaxDevices)) - 1; + return static_cast(value * static_cast(kMaxDevices)) - 1; } // Translate a PortMidi device ID to a VST parameter - static float DeviceIDToParameter(PmDeviceID index) + static float DeviceIDToParameter(MidiDevice::ID index) { return static_cast(index + 1) / static_cast(kMaxDevices); } @@ -125,10 +129,6 @@ #ifdef MODPLUG_TRACKER virtual CString GetDefaultEffectName() { return _T("MIDI Input / Output"); } - // Cache a range of names, in case one-by-one retrieval would be slow (e.g. when using plugin bridge) - virtual void CacheProgramNames(int32, int32) { } - virtual void CacheParameterNames(int32, int32) { } - virtual CString GetParamName(PlugParamIndex param); virtual CString GetParamLabel(PlugParamIndex) { return CString(); } virtual CString GetParamDisplay(PlugParamIndex param); @@ -155,18 +155,9 @@ protected: // Open a device for input or output. - void OpenDevice(PmDeviceID newDevice, bool asInputDevice); + void OpenDevice(MidiDevice::ID newDevice, bool asInputDevice); // Close an active device. void CloseDevice(MidiDevice &device); - // Get a device name - const char *GetDeviceName(PmDeviceID index) const; - // Check if the given device is an input device - bool IsInputDevice(PmDeviceID index) const; - // Check if the given device is an output device - bool IsOutputDevice(PmDeviceID index) const; - - // Get current timestamp for sending - PtTimestamp Now() const; }; Index: plugins/MidiInOut/MidiInOutEditor.cpp =================================================================== --- plugins/MidiInOut/MidiInOutEditor.cpp (revision 7555) +++ plugins/MidiInOut/MidiInOutEditor.cpp (working copy) @@ -14,6 +14,7 @@ #include "MidiInOutEditor.h" #include "../../mptrack/Mptrack.h" #include "../../mptrack/resource.h" +#include "../../include/rtmidi/RtMidi.h" OPENMPT_NAMESPACE_BEGIN @@ -75,32 +76,38 @@ int selectOutputItem = 0; MidiInOut &plugin = static_cast(m_VstPlugin); - const PmDeviceInfo *device; - CString deviceName; - - // Go through all PortMidi devices - for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++) + // Go through all RtMidi devices + unsigned int ports = plugin.midiIn.getPortCount(); + std::string portName; + for(unsigned int i = 0; i < ports; i++) { - if(device->input) + try { - // We can actually receive MIDI data on this device. - deviceName = theApp.GetFriendlyMIDIPortName(device->name, true) + _T(" [") + CString(device->interf) + _T("]"); - int result = m_inputCombo.AddString(deviceName); + portName = theApp.GetFriendlyMIDIPortName(plugin.inputDevice.GetPortName(i).c_str(), true); + int result = m_inputCombo.AddString(portName.c_str()); m_inputCombo.SetItemData(result, i); if(result != CB_ERR && i == plugin.inputDevice.index) selectInputItem = result; } + catch(RtMidiError &) + { + } + } - if(device->output) + ports = plugin.midiOut.getPortCount(); + for(unsigned int i = 0; i < ports; i++) + { + try { - // We can actually output MIDI data on this device. - deviceName = theApp.GetFriendlyMIDIPortName(device->name, false) + _T(" [") + CString(device->interf) + _T("]"); - int result = m_outputCombo.AddString(deviceName); + portName = theApp.GetFriendlyMIDIPortName(plugin.outputDevice.GetPortName(i).c_str(), false); + int result = m_outputCombo.AddString(portName.c_str()); m_outputCombo.SetItemData(result, i); if(result != CB_ERR && i == plugin.outputDevice.index) selectOutputItem = result; + } catch(RtMidiError &) + { } } @@ -113,13 +120,13 @@ // Refresh current input / output device in GUI -void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, PmDeviceID device) -//------------------------------------------------------------------------- +void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, MidiDevice::ID device) +//----------------------------------------------------------------------------- { int items = combo.GetCount(); for(int i = 0; i < items; i++) { - if(static_cast(combo.GetItemData(i)) == device) + if(static_cast(combo.GetItemData(i)) == device) { combo.SetCurSel(i); break; @@ -132,7 +139,7 @@ //------------------------------------------------------------------------------ { // Update device ID and notify plugin. - PmDeviceID newDevice = static_cast(combo.GetItemData(combo.GetCurSel())); + MidiDevice::ID newDevice = static_cast(combo.GetItemData(combo.GetCurSel())); plugin.SetParameter(param, MidiInOut::DeviceIDToParameter(newDevice)); plugin.AutomateParameter(param); } Index: plugins/MidiInOut/MidiInOutEditor.h =================================================================== --- plugins/MidiInOut/MidiInOutEditor.h (revision 7555) +++ plugins/MidiInOut/MidiInOutEditor.h (working copy) @@ -13,7 +13,6 @@ #ifdef MODPLUG_TRACKER #include "../../mptrack/AbstractVstEditor.h" -#include OPENMPT_NAMESPACE_BEGIN @@ -32,7 +31,7 @@ MidiInOutEditor(MidiInOut &plugin); // Refresh current input / output device in GUI - void SetCurrentDevice(bool asInputDevice, PmDeviceID device) + void SetCurrentDevice(bool asInputDevice, MidiDevice::ID device) { CComboBox &combo = asInputDevice ? m_inputCombo : m_outputCombo; SetCurrentDevice(combo, device); @@ -47,7 +46,7 @@ // Update lists of available input / output devices void PopulateLists(); // Refresh current input / output device in GUI - void SetCurrentDevice(CComboBox &combo, PmDeviceID device); + void SetCurrentDevice(CComboBox &combo, MidiDevice::ID device); virtual void DoDataExchange(CDataExchange* pDX);