Author |
Topic: C# Wrapper for MoxScript, with Transpose Example (Read 10059 times) |
|
SimonO
New Member
MIDI-OX Rules!
Gender:
Posts: 10
|
|
C# Wrapper for MoxScript, with Transpose Example
« on: Dec 29th, 2010, 12:57am » |
Quote Modify
|
In this thread, I'm not actually asking a question, just providing information. I'm going to share my code for a MoxScript wrapper in the C# programming language. This may be of interest not only to C# programmers but also to programmers using the Visual Basic .NET programming language or other programming languages that are based on the .NET Framework. The core of the code is the way .NET Framework interfaces are used to handle the events raised by MoxScript. That's going to be much the same in any .NET programming language. I wrote the code for .NET Framework version 3.5. The code would have to be adjusted slightly to work with .NET Framework version 2. Embedded XML comments in the code provide documentation. So I should not need to say much else by way of explanation. But I had better say something about the way the code handles MoxScript's events. There are different ways in which classes in COM components can raise events. If a COM component implements the IDespatch interface, the events raised by that COM component's classes are automatically exposed to .NET programming languages in exactly the same way as events raised by .NET classes. That applies to COM components written in Visual Basic 6 for example, where IDespatch is implemented under the hood, without the programmer needing to know about it. MIDI-OX clearly is not written in VB6, because MoxScript's events are not automatically exposed to .NET programming languages. Instead, MoxScript raises its events via a connection point. In .NET, we need to code a connection point sink in order to handle MoxScript's events. The approach I have taken is to hide the complexity of the connection point sink by raising standard .NET events whenever one of MoxScript's events is raised. So first we need two standard .NET event argument classes. Here's the one for the MidiInput event: Code:using System; using System.ComponentModel; namespace MidiEventer.Scripting { /// <summary> /// Arguments for a MidiInput event. /// </summary> public class MidiInputEventArgs : CancelEventArgs { #region Properties /// <summary> /// Gets or sets the Data 1 value. /// </summary> public int Data1 { get; set; } /// <summary> /// Gets or sets the Data 2 value. /// </summary> public int Data2 { get; set; } /// <summary> /// Gets the port. /// </summary> public int Port { get; private set; } /// <summary> /// Gets or sets the status. /// </summary> public int Status { get; set; } /// <summary> /// Gets the timestamp. /// </summary> public int Timestamp { get; private set; } #endregion Properties #region Constructors /// <summary> /// Initialises a new instance of the /// <see cref="MidiInputEventArgs"/> /// class. /// </summary> /// <param name="timestamp">Timestamp</param> /// <param name="port">Port</param> /// <param name="status">Status</param> /// <param name="data1">Data value 1</param> /// <param name="data2">Data value 2</param> public MidiInputEventArgs( int timestamp, int port, int status, int data1, int data2) { Timestamp = timestamp; Port = port; Status = status; Data1 = data1; Data2 = data2; } #endregion Constructors }//End of class }//End of namespace |
| And here's the event argument class for the SysExInput event: Code:using System; using System.ComponentModel; namespace MidiEventer.Scripting { /// <summary> /// Arguments for a SysExInput event. /// </summary> public class SysExInputEventArgs : CancelEventArgs { #region Properties /// <summary> /// Gets or sets the System Exclusive message string. /// </summary> public string SysExMessage { get; set; } #endregion Properties #region Constructors /// <summary> /// Initialises a new instance of the /// <see cref="SysExInputEventArgs"/> /// class. /// </summary> /// <param name="sysExMessage"> /// The System Exclusive message string. /// </param> public SysExInputEventArgs(string sysExMessage) { SysExMessage = sysExMessage; } #endregion Constructors }//End of class }//End of namespace |
| I'm getting close to the the maximum length of a post. So I shall provide the remainder of the explanation and code in additional posts.
|
|
IP Logged |
|
|
|
SimonO
New Member
MIDI-OX Rules!
Gender:
Posts: 10
|
|
C# Wrapper for MoxScript, with Transpose Example
« Reply #1 on: Dec 29th, 2010, 1:00am » |
Quote Modify
|
The above two event argument classes are used to raise standard .NET events by the following connection point sink class, which has to implement the MIDIOXLib._IMoxScriptEvents interface. Note that I have had to prefix the .NET event names with underscores to distinguish them from the method names required for the connection point sink by _ImoxScriptEvents. But the connection point sink class is internal, so this bit of ugliness will be hidden from the scripting application. Here's the code for MoxScriptEvents, the connection point sink class: Code:using System; namespace MidiEventer.Scripting { /// <summary> /// A connection point sink for handling the events /// of a MIDI-OX script. /// </summary> internal class MoxScriptEvents : MIDIOXLib._IMoxScriptEvents { #region Events /// <summary> /// This event is raised /// whenever MIDI data arrives. /// </summary> public event EventHandler<MidiInputEventArgs> _MidiInput; /// <summary> /// This event is raised /// whenever System Exclusive (SysEx) data arrives. /// </summary> public event EventHandler<SysExInputEventArgs> _SysExInput; #endregion Events #region Event Handlers /// <summary> /// This event is raised by MIDI-OX /// whenever MIDI data arrives. /// </summary> /// <param name="timestamp">Timestamp</param> /// <param name="port">Port</param> /// <param name="status">Status</param> /// <param name="data1">Data value 1</param> /// <param name="data2">Data value 2</param> public void MidiInput(int timestamp, int port, int status, int data1, int data2) { if (_MidiInput != null) { _MidiInput( this, new MidiInputEventArgs( timestamp, port, status, data1, data2)); } } /// <summary> /// This event is raised by MIDI-OX /// whenever System Exclusive (SysEx) data arrives. /// </summary> /// <param name="sysExMessage"> /// The System Exclusive message string. /// </param> public void SysExInput(string sysExMessage) { if (_SysExInput != null) { _SysExInput( this, new SysExInputEventArgs(sysExMessage)); } } #endregion Event Handlers }//End of class }//End of namespace |
|
|
« Last Edit: Dec 29th, 2010, 1:01am by SimonO » |
IP Logged |
|
|
|
SimonO
New Member
MIDI-OX Rules!
Gender:
Posts: 10
|
|
C# Wrapper for MoxScript, with Transpose Example
« Reply #2 on: Dec 29th, 2010, 1:14am » |
Quote Modify
|
The wrapper class, called MidiOx, handles the events raised by an instance of MoxScriptEvents, the connection point sink class, to raise public standard NET events corresponding to the connection point sink's events, this time without the underscore prefixes on the event names. Here's part one of the code for MidiOx, the wrapper class: Code:using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using MIDIOXLib; namespace MidiEventer.Scripting { /// <summary> /// This class provides control of MIDI-OX /// via a script. /// </summary> public class MidiOx : IDisposable { #region Events /// <summary> /// This event is raised /// whenever MIDI data arrives. /// </summary> /// <remarks> /// If the event is handled, /// the MIDI message passed on to MIDI-OX /// can by modified by updating the /// <see cref="MidiInputEventArgs.Status"/>, /// <see cref="MidiInputEventArgs.Data1"/> and /// <see cref="MidiInputEventArgs.Data2"/> /// properties of the event arguments object. /// Alternatively, /// the MIDI message can be suppressed by /// setting the <see cref="CancelEventArgs.Cancel"/> /// property of the event arguments object /// to <b>True</b>. /// Additional MIDI messages can be sent /// to MIDI-OX by invoking the /// <see cref="SendMidiMessage"/> method. /// System Exclusive (SysEx) messages can be sent /// to MIDI-OX by invoking the /// <see cref="SendSysExMessage"/> method. /// </remarks> public event EventHandler<MidiInputEventArgs> MidiInput; /// <summary> /// This event is raised /// whenever System Exclusive (SysEx) data arrives. /// </summary> /// <remarks> /// If the event is handled, /// the System Exclusive (SysEx) message passed on to MIDI-OX /// can by modified by updating the /// <see cref="SysExInputEventArgs.SysExMessage"/> /// property of the event arguments object. /// Alternatively, /// the System Exclusive message can be suppressed by /// setting the <see cref="CancelEventArgs.Cancel"/> /// property of the event arguments object /// to <b>True</b>. /// Additional System Exclusive messages can be sent /// to MIDI-OX by invoking the /// <see cref="SendSysExMessage"/> method. /// MIDI messages can be sent /// to MIDI-OX by invoking the /// <see cref="SendMidiMessage"/> method. /// </remarks> public event EventHandler<SysExInputEventArgs> SysExInput; #endregion Events #region Fields private int _connectionCookie; private IConnectionPoint _connectionPoint; private bool _isDisposed = false; private MoxScript _mox; #endregion Fields #region Constructors /// <summary> /// Initialises a new instance of the /// <see cref="MidiOx"/> /// class. /// </summary> /// <remarks> /// A new instance of MIDI-OX /// will be started if one is not already running. /// The script will be started. /// </remarks> public MidiOx() { _mox = new MoxScript(); HandleEvents(); _mox.DivertMidiInput = 1; _mox.FireMidiInput = 1; } #endregion Constructors |
| The code for the MidiOx class will be concluded in the next post.
|
|
IP Logged |
|
|
|
SimonO
New Member
MIDI-OX Rules!
Gender:
Posts: 10
|
|
C# Wrapper for MoxScript, with Transpose Example
« Reply #3 on: Dec 29th, 2010, 1:17am » |
Quote Modify
|
Here's the remainder of the code for the MidiOx class: Code: #region Event Handlers /// <summary> /// Handles the connection point sink's /// <see cref="MoxScriptEvents._MidiInput"/> /// event to raise the <see cref="MidiInput"/> event. /// </summary> /// <param name="sender">Event sender.</param> /// <param name="e">Event arguments.</param> private void Events_MidiInput(object sender, MidiInputEventArgs e) { if (MidiInput != null) { MidiInput(this, e); if (e.Cancel) { return; } } SendMidiMessage(e.Status, e.Data1, e.Data2); } /// <summary> /// Handles the connection point sink's /// <see cref="MoxScriptEvents._SysExInput"/> /// event to raise the <see cref="SysExInput"/> event. /// </summary> /// <param name="sender">Event sender.</param> /// <param name="e">Event arguments.</param> private void Events_SysExInput(object sender, SysExInputEventArgs e) { if (SysExInput != null) { SysExInput(this, e); if (e.Cancel) { return; } } SendSysExMessage(e.SysExMessage); } #endregion Event Handlers #region Public Methods /// <summary> /// Disposes of unmanaged resources /// and closes the MIDI-OX instance. /// </summary> public void Dispose() { if (_isDisposed) { return; } _isDisposed = true; try { _mox.DivertMidiInput = 0; _mox.FireMidiInput = 0; UnhandleEvents(); } catch (InvalidComObjectException) { // Thrown if the MIDI-OX application instance // has already been closed. } _mox = null; // The Midi-Ox application won't close // unless we force a garbage collection // after uninstantiating the MoxScript. GC.Collect(); } /// <summary> /// Sends a MIDI message for all MIDI ports that are mapping the channel. /// </summary> /// <param name="status">Status</param> /// <param name="data1">Data value 1</param> /// <param name="data2">Data value 2</param> public void SendMidiMessage(int status, int data1, int data2) { // Setting the port to -1 works. // For unknown reason, // setting the port to the input port does not work. _mox.OutputMidiMsg(-1, status, data1, data2); } /// <summary> /// Sends a System Exclusive (SysEx) message string. /// </summary> /// <param name="sysExMessage"> /// The System Exclusive message string. /// </param> public void SendSysExMessage(string sysExMessage) { _mox.SendSysExString(sysExMessage); } #endregion Public Methods #region Private Methods /// <summary> /// Creates a connection point sink to /// handle the events raised by MIDI-OX. /// </summary> private void HandleEvents() { IConnectionPointContainer connectionPointContainer = (IConnectionPointContainer)_mox; // UUID of _IMoxScriptEvents Interface Guid guid = new Guid("604C848E-5054-11D5-9B01-006008C4F4C8"); connectionPointContainer.FindConnectionPoint(ref guid, out _connectionPoint); MoxScriptEvents events = new MoxScriptEvents(); _connectionPoint.Advise(events, out _connectionCookie); events._MidiInput += new EventHandler<MidiInputEventArgs>(Events_MidiInput); events._SysExInput += new EventHandler<SysExInputEventArgs>(Events_SysExInput); } /// <summary> /// Closes the connection point sink /// that handled the events raised by MIDI-OX. /// </summary> private void UnhandleEvents() { // A connection that is created by IConnectionPoint.Advise // must be closed by calling IConnectionPoint.Unadvise // to avoid a memory leak. _connectionPoint.Unadvise(_connectionCookie); _connectionPoint = null; } #endregion Private Methods }//End of class }//End of namespace |
| That concludes the code for the wrapper assembly.
|
|
IP Logged |
|
|
|
SimonO
New Member
MIDI-OX Rules!
Gender:
Posts: 10
|
|
C# Wrapper for MoxScript, with Transpose Example
« Reply #4 on: Dec 29th, 2010, 1:24am » |
Quote Modify
|
Here's a simple Windows Forms example using the wrapper. The Windows Forms project needs a reference to the MidiEventer.Scripting assembly we have just created. It does not need a reference to the MIDI-OX COM comonent. On a Windows form, place a Start button and a Stop button. Handle the Click events of the buttons. Because we want to shut down MIDI-OX when the form closes if not already done by manually or by the Stop button, we also need a handler for the form's FormClosed event: Code:#region Fields MidiOx _midiOx; #endregion Fields #region Event Handlers private void Form1_FormClosed(object sender, FormClosedEventArgs e) { StopButton_Click(null, null); } private void MidiOx_MidiInput(object sender, MidiInputEventArgs e) { switch (e.Status) { case 128: // Hex 80 case 144: // Hex 90 // Note. // e.Data1 is the pitch. // e.Data2 is velocity for Note On, 0 for Note Off. // Transpose the note up an octave. e.Data1 += 12; break; }//End of switch (e.Status) } private void StartButton_Click(object sender, EventArgs e) { _midiOx = new MidiOx(); _midiOx.MidiInput += new EventHandler<MidiInputEventArgs>(MidiOx_MidiInput); } private void StopButton_Click(object sender, EventArgs e) { if (_midiOx != null) { _midiOx.Dispose(); _midiOx = null; } } #endregion Event Handlers |
|
|
|
IP Logged |
|
|
|
SimonO
New Member
MIDI-OX Rules!
Gender:
Posts: 10
|
|
C# Wrapper for MoxScript, with Transpose Example
« Reply #5 on: Dec 29th, 2010, 1:30am » |
Quote Modify
|
Here's a much more complex real Windows Forms example using the wrapper. The specific scripting application may be of interest. MIDI control change 77 (hex 4D) can be used for the transposition of notes. But my synth does not implement it. So I wrote a MIDI-OX script in C# to do it. I use programmable footswitches, in my case on a Roland FC-300 Foot Controller, to send control change 77. One footswitch sends control change 77 with value 52 when down and 0 when up. I wanted this footswitch to transpose the pitch down an octave every time it was clicked, so two octaves down if clicked twice. Another footswitch sends control change 77 with value 76 when down and 0 when up. I wanted this footswitch to transpose the pitch up an octave every time it was clicked, so two octaves up if clicked twice. The values 52 and 77 indicate the number of semitones relative to the mid-point 64 which, if sent, would indicate no change to the number of semitones to be transposed. So additional footswitches can be added for two-octave changes to the transposition by sending 40 for down two octaves and 88 for up two octaves. The footswitches are programmed to operate in momentary mode rather than in latching mode, but I don't want the transposition to be cancelled when I take my foot off a footswitch. So, if control change 77 with value 0 is received by the script, which happens when I take my foot off one of the two footswitches, it needs to be ignored. The code for this more complex example will follow in the next post.
|
|
IP Logged |
|
|
|
SimonO
New Member
MIDI-OX Rules!
Gender:
Posts: 10
|
|
C# Wrapper for MoxScript, with Transpose Example
« Reply #6 on: Dec 29th, 2010, 1:31am » |
Quote Modify
|
The code for this example is for the same Windows form with the two buttons as for the simple example above. The only differences are two additional class-level fields and different code in the handler for the MidiOx object's MidiInput event. So here's what's different: Code:#region Fields MidiOx _midiOx; private int _transpose = 0; private bool _transposeStarting = false; #endregion Fields private void MidiOx_MidiInput(object sender, MidiInputEventArgs e) { switch (e.Status) { case 128: // Hex 80 case 144: // Hex 90 // Note. // e.Data1 is the pitch. // e.Data2 is velocity for Note On, 0 for Note Off. // // If the pitch would be out of range (0 to 127) // with the proposed transposition, // adjust the pitch and transposition // by just enough octaves to bring the note // into range. int newNote = e.Data1 + _transpose; if (newNote < 0) { _transposeStarting = true; while (newNote < 0) { _transpose += 12; newNote += 12; } } else if (newNote > 127) { _transposeStarting = true; while (newNote > 127) { _transpose -= 12; newNote -= 12; } } // If the number of semitones by which notes are // to be transposed has just been changed, // either in response to a control change #77 // or because the pitch would be out of range // with the proposed transposition, // send All Notes Off. // If we don't do this, // notes that were still on // when we started transposing // will never get turned off. if (_transposeStarting) { _transposeStarting = false; // Status parameter set to // 176 for channel 1 // 177 for channel 2 etc. _midiOx.SendMidiMessage(176, 123, 0); } e.Data1 = newNote; break; case 176: // Hex B0 // Control change. // e.Data1 is the control change number. // e.Data2 is the control change value. switch (e.Data1) { case 77: // Hex 4D // Transpose. // Not implemented by the synth, // so I'm doing my own version here. // Momentary footswitches that send value 0 // when off are being used for Transpose. // But we want the Transpose to apply // till another Transpose switch is clicked, // not just when the switch is down. // So ignore value 0. if (e.Data2 != 0) { // Transposition is relative. // Value 64 (mid-point) means // the note is not to be further transposed. // Other values are number of semitones // relative to that. // E.g. 76 = 64 + 12 = // transpose up an octave more than was already transposed. _transpose += e.Data2 - 64; _transposeStarting = true; } // Don't pass the Transpose message on to the synth, // as we have dealt with it here. e.Cancel = true; break; }//End of switch (e.Data1) break; }//End of switch (e.Status) } |
| That concludes the example code. And that's my final post in the series. Unless there are any questions etc...
|
|
IP Logged |
|
|
|
|