837 lines
22 KiB
C#
837 lines
22 KiB
C#
/*
|
|
|
|
Copyright (C) Arnaud Houdelette 2012-2014
|
|
Copyright (C) Emmanuel Langlois 2012-2014
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace DMX2
|
|
{
|
|
public class MidiEventProvider : IEventProvider, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Etat interne des evenements midi paginés.
|
|
/// </summary>
|
|
readonly Dictionary<string,internalEventDesc> eventlist = new Dictionary<string, internalEventDesc> ();
|
|
|
|
/// <summary>
|
|
/// Liste des peripheriques connus (presents ou non)
|
|
/// </summary>
|
|
readonly Dictionary<string,MidiDevice> knowndevices = new Dictionary<string,MidiDevice> ();
|
|
|
|
/// <summary>
|
|
/// Liste des ports connectés avec feedback
|
|
/// </summary>
|
|
readonly List<int> feedbacksources = new List<int> ();
|
|
|
|
//static readonly Dictionary<int,MidiDev> srcidToDev = new Dictionary<int, MidiDev>();
|
|
readonly List<byte> unpaginatedchannels = new List<byte> ();
|
|
|
|
/// <summary>
|
|
/// Derniere valeur connue pour une evenement sur source donnée :
|
|
/// Soit recue, soit envoyée (feedback / changement de page)
|
|
/// </summary>
|
|
readonly Dictionary<int,byte> lastValueOfSrc = new Dictionary<int, byte> ();
|
|
// EventData last;
|
|
internalEventDesc levent = null;
|
|
bool guirefreshflag = false;
|
|
uint page = 1;
|
|
uint maxpage = 8;
|
|
|
|
protected bool use14b = false;
|
|
protected int max14bValue = 100;
|
|
byte[] fbTmpData = new byte[512];
|
|
uint pageUpCC = 127;
|
|
uint pageDownCC = 126;
|
|
|
|
int eventmidiport;
|
|
|
|
public uint CurrentPage {
|
|
get {
|
|
return page;
|
|
}
|
|
set {
|
|
if (value < 1 || value > maxpage)
|
|
return;
|
|
page = value;
|
|
Refresh ();
|
|
}
|
|
}
|
|
public uint PageUpCC {
|
|
get {
|
|
return pageUpCC;
|
|
}
|
|
set {
|
|
pageUpCC = value;
|
|
}
|
|
}
|
|
public uint PageDownCC {
|
|
get {
|
|
return pageDownCC;
|
|
}
|
|
set {
|
|
pageDownCC = value;
|
|
}
|
|
}
|
|
|
|
public uint Maxpage {
|
|
get {
|
|
return maxpage;
|
|
}
|
|
set {
|
|
maxpage = value;
|
|
}
|
|
}
|
|
public List<byte> UnpaginatedChannels {
|
|
get {
|
|
return unpaginatedchannels;
|
|
}
|
|
}
|
|
|
|
public bool Use14bCC {
|
|
get {
|
|
return use14b;
|
|
}
|
|
set {
|
|
use14b = value;
|
|
}
|
|
}
|
|
public int Max14bValue {
|
|
get {
|
|
return max14bValue;
|
|
}
|
|
set {
|
|
max14bValue = value;
|
|
}
|
|
}
|
|
|
|
public bool GuiRefreshFlag {
|
|
get {
|
|
if (guirefreshflag) {
|
|
guirefreshflag = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void ConnectDevice (string name)
|
|
{
|
|
knowndevices.Add (name, new MidiDevice (name));
|
|
AutoConnect ();
|
|
}
|
|
|
|
public void RefreshFeedback (string name)
|
|
{
|
|
foreach (int port in knowndevices[name].ConnectedPorts) {
|
|
if (knowndevices [name].HasFeedback) {
|
|
if (!feedbacksources.Contains (port))
|
|
feedbacksources.Add (port);
|
|
} else {
|
|
if (feedbacksources.Contains (port))
|
|
feedbacksources.Remove (port);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void DisconnectDevice (MidiEventProvider.MidiDevice dev)
|
|
{
|
|
if (!knowndevices.ContainsKey (dev.Name))
|
|
return;
|
|
knowndevices.Remove (dev.Name);
|
|
|
|
foreach (int connectedport in dev.ConnectedPorts) {
|
|
int client = connectedport >> 8;
|
|
int port = connectedport & 0xFF;
|
|
AlsaSeqLib.Deconnecte (eventmidiport, client, port);
|
|
}
|
|
}
|
|
|
|
public bool IsKnownDevice (string name)
|
|
{
|
|
return knowndevices.ContainsKey (name);
|
|
}
|
|
|
|
public IEnumerable<MidiDevice> KnownDevices {
|
|
get {
|
|
return knowndevices.Values;
|
|
}
|
|
}
|
|
|
|
public MidiEventProvider (EventManager manager)
|
|
{
|
|
#if DEBUG
|
|
MidiDevice dev = new MidiDevice("VMPK Input:VMPK Input");
|
|
dev.HasFeedback = true;
|
|
knowndevices.Add(dev.Name,dev);
|
|
dev = new MidiDevice("VMPK Output:VMPK Output");
|
|
dev.HasFeedback = true;
|
|
knowndevices.Add(dev.Name,dev);
|
|
#endif
|
|
manager.RegisterProvider (this);
|
|
//AlsaSeqLib.Init ();
|
|
|
|
eventmidiport = AlsaSeqLib.CreatePort ("event_prov_in_out");
|
|
|
|
AlsaSeqLib.ConnectFrom (eventmidiport,AlsaSeqLib.SND_SEQ_CLIENT_SYSTEM, AlsaSeqLib.SND_SEQ_PORT_SYSTEM_ANNOUNCE);
|
|
|
|
AutoConnect ();
|
|
}
|
|
|
|
void AutoConnect ()
|
|
{
|
|
foreach (var cli in AlsaSeqLib.EnumClients()) {
|
|
foreach (var p in cli.Ports) {
|
|
PortDetected (cli, p);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PortDetected (AlsaSeqLib.Client cli, AlsaSeqLib.Port p)
|
|
{
|
|
// Execute a chaque 'apparition' d'un port midi
|
|
|
|
// teste si connection auto au port et connecte si besoin
|
|
|
|
guirefreshflag = true;
|
|
|
|
string fullportname = cli.Name + ':' + p.Name;
|
|
if (knowndevices.ContainsKey (fullportname)) {
|
|
int srcid = p.ClientId << 8 + p.PortId;
|
|
if (knowndevices [fullportname].ConnectedPorts.Contains (srcid))
|
|
return;
|
|
|
|
AlsaSeqLib.Connect (eventmidiport,p);
|
|
}
|
|
}
|
|
|
|
void PortConnect (AlsaSeqLib.snd_seq_connect_t cn, bool connect)
|
|
{
|
|
int clientId, portId;
|
|
if (cn.dest.client == AlsaSeqLib.ClientId) {
|
|
clientId = cn.sender.client;
|
|
portId = cn.sender.port;
|
|
} else {
|
|
clientId = cn.dest.client;
|
|
portId = cn.dest.port;
|
|
}
|
|
|
|
int srcid = clientId << 8 + portId;
|
|
if (connect) {
|
|
string fpname = AlsaSeqLib.GetClientByID (clientId).Name + ":" + AlsaSeqLib.GetPortByIDs (clientId, portId).Name;
|
|
if (!knowndevices.ContainsKey (fpname))
|
|
return;
|
|
if (knowndevices [fpname].ConnectedPorts.Contains (srcid))
|
|
return;
|
|
knowndevices [fpname].ConnectedPorts.Add (srcid);
|
|
if (knowndevices [fpname].HasFeedback)
|
|
feedbacksources.Add (srcid);
|
|
guirefreshflag = true;
|
|
|
|
//srcidToDev[srcid] = knowndevices [fpname];
|
|
|
|
return;
|
|
}
|
|
|
|
foreach (var dev in knowndevices.Values) {
|
|
if (dev.ConnectedPorts.Contains (srcid)) {
|
|
/*if(srcidToDev.ContainsKey(srcid))
|
|
srcidToDev.Remove(srcid);*/
|
|
dev.ConnectedPorts.Remove (srcid);
|
|
guirefreshflag = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static int CombineHash (int hash1, int hash2)
|
|
{
|
|
unchecked {
|
|
return hash1 * 33 + hash2;
|
|
}
|
|
}
|
|
|
|
protected bool HasFeedback (int source)
|
|
{
|
|
return feedbacksources.Contains (source);
|
|
}
|
|
|
|
public void SendEvent (AlsaSeqLib.snd_seq_event_t ev)
|
|
{
|
|
ev.source.port =(byte) eventmidiport;
|
|
AlsaSeqLib.SendEventToSubscribers (ev);
|
|
}
|
|
|
|
public void Refresh ()
|
|
{
|
|
foreach (var ievent in eventlist.Values) {
|
|
if (ievent.Page == page) {
|
|
ievent.SendFeedback();
|
|
foreach (int src in feedbacksources) {
|
|
int lnvk = CombineHash (src, ievent.MidiEvCode);
|
|
if (ievent.LastKnownValue != -1)
|
|
lastValueOfSrc [lnvk] = (byte)ievent.LastKnownValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#region IEventProvider implementation
|
|
|
|
|
|
|
|
bool IEventProvider.Bind (string eventId)
|
|
{
|
|
// On indique a l'EventManager qu'on traite, si l'ID commence par 'MIDI-'
|
|
if (! eventId.StartsWith ("MIDI-"))
|
|
return false;
|
|
|
|
if (! eventlist.ContainsKey (eventId)) {
|
|
Match res = regexEventID.Match (eventId);
|
|
if (!res.Success)
|
|
return false;
|
|
uint _page = uint.Parse (res.Groups ["page"].Value);
|
|
int _evHC = res.Groups ["id"].Value.GetHashCode ();
|
|
midiFeedbackSender sender = null;
|
|
|
|
res = regexCtrlEventID.Match (eventId);
|
|
if (res.Success) {
|
|
byte chan = byte.Parse (res.Groups ["chan"].Value);
|
|
uint param = uint.Parse (res.Groups ["param"].Value);
|
|
sender = new midiCCFbSender (this, chan, param);
|
|
} else if ((res = regexPbEventID.Match (eventId)).Success){
|
|
byte chan = byte.Parse (res.Groups ["chan"].Value);
|
|
sender = new midiPBFbSender (this, chan);
|
|
}
|
|
eventlist.Add (eventId, new internalEventDesc (eventId, _page, _evHC,sender));
|
|
}
|
|
|
|
eventlist [eventId].Bound = true;
|
|
return true;
|
|
}
|
|
|
|
void IEventProvider.Unbind (string eventId)
|
|
{
|
|
if (! eventlist.ContainsKey (eventId))
|
|
return;
|
|
eventlist [eventId].Bound = false;
|
|
return;
|
|
}
|
|
|
|
Gtk.Menu IEventProvider.GetProviderSubMenu (EventManager.EventMenuData state, Gtk.ButtonPressEventHandler handler)
|
|
{
|
|
Gtk.Menu retmenu = new Gtk.Menu ();
|
|
|
|
if (levent != null) { // Creation du sous menu "Dernier"
|
|
/*Gtk.MenuItem lmenuitem = new Gtk.MenuItem ("Dernier");
|
|
retmenu.Add (lmenuitem);
|
|
Gtk.Menu lmenu = new Gtk.Menu ();
|
|
lmenuitem.Submenu = lmenu;*/
|
|
Gtk.MenuItem item = new Gtk.MenuItem (GetDescription (levent.InternalName));
|
|
item.Data [EventManager.EventIdKey] = levent.InternalName;
|
|
item.Data [EventManager.StateKey] = state;
|
|
item.ButtonPressEvent += handler;
|
|
retmenu.Add (item);
|
|
}
|
|
|
|
Gtk.MenuItem evmenuitem = new Gtk.MenuItem ("Events"); // Creation du sous menu "Events"
|
|
retmenu.Add (evmenuitem);
|
|
Gtk.Menu evmenu = new Gtk.Menu ();
|
|
evmenuitem.Submenu = evmenu;
|
|
|
|
List<string> sortedKeys = eventlist.Keys.ToList (); // On recupere des IDs
|
|
sortedKeys.Sort (); // et on les trie
|
|
|
|
foreach (string key in sortedKeys) {
|
|
internalEventDesc evt = eventlist [key];
|
|
Gtk.MenuItem item = new Gtk.MenuItem (GetDescription (evt.InternalName));
|
|
item.Data [EventManager.EventIdKey] = evt.InternalName;
|
|
item.Data [EventManager.StateKey] = state;
|
|
item.ButtonPressEvent += handler;
|
|
evmenu.Add (item);
|
|
}
|
|
|
|
return retmenu;
|
|
|
|
}
|
|
|
|
void IEventProvider.ProcessEvents (EventManagerCallback callback)
|
|
{
|
|
AlsaSeqLib.snd_seq_event_t evS;
|
|
uint evpage;
|
|
|
|
// Tant qu'il y des evenements midi en attente
|
|
while (AlsaSeqLib.GetEvent(out evS)) {
|
|
Console.WriteLine(string.Format ("event {0}", evS.type) );
|
|
string id = null;
|
|
int value = 0;
|
|
byte channel = 255;
|
|
|
|
switch (evS.type) {
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_PORT_SUBSCRIBED: // Connection d'un périph midi
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
|
|
PortConnect (
|
|
evS.data_connect,
|
|
evS.type == AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_PORT_SUBSCRIBED
|
|
);
|
|
continue;
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_CONTROLLER:
|
|
if (evS.data_ev_ctrl.param == pageUpCC && evS.data_ev_ctrl.value > 0) {
|
|
CurrentPage++;
|
|
continue;
|
|
}
|
|
|
|
if (evS.data_ev_ctrl.param == pageDownCC && evS.data_ev_ctrl.value > 0) {
|
|
CurrentPage--;
|
|
continue;
|
|
}
|
|
|
|
channel = evS.data_ev_ctrl.channel;
|
|
|
|
if (use14b && evS.data_ev_ctrl.param < 64) {
|
|
long msbAddr;
|
|
if (evS.data_ev_ctrl.param < 32) {
|
|
msbAddr = evS.data_ev_ctrl.channel * 32 + evS.data_ev_ctrl.param;
|
|
fbTmpData [msbAddr] = (byte)evS.data_ev_ctrl.value;
|
|
continue;
|
|
}
|
|
evS.data_ev_ctrl.param -= 32;
|
|
msbAddr = evS.data_ev_ctrl.channel * 32 + evS.data_ev_ctrl.param;
|
|
value = ((fbTmpData [msbAddr] << 7) ^ evS.data_ev_ctrl.value);
|
|
value = 255 * value / max14bValue;
|
|
if (value > 255) value = 255;
|
|
} else {
|
|
value = 255 * evS.data_ev_ctrl.value / 127; // Conversion {0,127} => {0,255}
|
|
}
|
|
id = string.Format ("CTRL-C{0}P{1}", evS.data_ev_ctrl.channel, evS.data_ev_ctrl.param);
|
|
break;
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_NOTEON:
|
|
id = string.Format ("NOTE-C{0}N{1}", evS.data_ev_note.channel, evS.data_ev_note.note);
|
|
channel = evS.data_ev_note.channel;
|
|
value = 255;
|
|
break;
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_NOTEOFF:
|
|
id = string.Format ("NOTE-C{0}N{1}", evS.data_ev_note.channel, evS.data_ev_note.note);
|
|
channel = evS.data_ev_note.channel;
|
|
value = 0;
|
|
break;
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_PITCHBEND:
|
|
id = string.Format ("PB-C{0}", evS.data_ev_ctrl.channel);
|
|
channel = evS.data_ev_ctrl.channel;
|
|
value = ((evS.data_ev_ctrl.value + 7000) * 255 / 14000);
|
|
if (value < 0)
|
|
value = 0;
|
|
if (value > 255)
|
|
value = 255;
|
|
break;
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_PGMCHANGE:
|
|
CurrentPage = (uint)evS.data_ev_ctrl.value;
|
|
continue;
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_PORT_START:
|
|
PortDetected (
|
|
AlsaSeqLib.GetClientByID (evS.data_addr.client),
|
|
AlsaSeqLib.GetPortByIDs (evS.data_addr.client, evS.data_addr.port)
|
|
);
|
|
continue;
|
|
|
|
/*case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_CLOCK:
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_SENSING:
|
|
continue;*/
|
|
|
|
default:
|
|
id = null;
|
|
#if DEBUG
|
|
Console.WriteLine(string.Format ("event {0}", evS.type) );
|
|
Info.Publish(string.Format ("event {0}", evS.type) ); // On affiche les evenements inconnus
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
if (id != null) {
|
|
// Hashcode de l'ev Midi, non pagine
|
|
int evHC = id.GetHashCode ();
|
|
int srcid = evS.source.client << 8 + evS.source.port;
|
|
int lnvk = CombineHash (srcid, evHC);
|
|
|
|
if (channel == 255 || unpaginatedchannels.Contains (channel))
|
|
evpage = 0;
|
|
else
|
|
evpage = page;
|
|
|
|
// Construction de l'ID evenement
|
|
id = string.Format ("MIDI-PAGE{0}-{1}", evpage, id);
|
|
|
|
// Creation de l'objet interne si innexistant
|
|
if (!eventlist.ContainsKey (id)) {
|
|
switch (evS.type) {
|
|
// TODO : Pitchbend feedback
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_CONTROLLER:
|
|
eventlist.Add (id, new internalEventDesc (id, evpage, evHC,
|
|
new midiCCFbSender(this,channel,evS.data_ev_ctrl.param))
|
|
);
|
|
break;
|
|
case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_PITCHBEND:
|
|
eventlist.Add (id, new internalEventDesc (id, evpage, evHC,
|
|
new midiPBFbSender(this,channel))
|
|
);
|
|
break;
|
|
default:
|
|
eventlist.Add (id, new internalEventDesc (id, evpage, evHC, null));
|
|
break;
|
|
}
|
|
}
|
|
levent = eventlist [id]; //Dernier Evenement recu conserve pour menu
|
|
|
|
if (!lastValueOfSrc.ContainsKey (lnvk)) {
|
|
lastValueOfSrc [lnvk] = (byte)value;
|
|
} else if (lastValueOfSrc [lnvk] == (byte)value)
|
|
continue;
|
|
|
|
EventData evData = new EventData ();
|
|
evData.id = id;
|
|
evData.value = (byte)value;
|
|
evData.prev_value = lastValueOfSrc [lnvk];
|
|
/*if (evData.Equals (last))
|
|
continue; */
|
|
//last = evData;
|
|
|
|
eventlist [id].CallEvent (callback, evData);
|
|
|
|
/*if (eventlist [id].Bound) {
|
|
callback (evData);
|
|
}*/
|
|
|
|
lastValueOfSrc [lnvk] = (byte)value;
|
|
eventlist [id].LastKnownValue = (byte)value;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
string IEventProvider.MenuName {
|
|
get {
|
|
return "Midi";
|
|
}
|
|
}
|
|
|
|
static System.Text.RegularExpressions.Regex regexEventID = new System.Text.RegularExpressions.Regex (
|
|
@"MIDI-PAGE(?<page>\d+)-(?<id>.+)",
|
|
System.Text.RegularExpressions.RegexOptions.Compiled);
|
|
|
|
static System.Text.RegularExpressions.Regex regexCtrlEventID = new System.Text.RegularExpressions.Regex (
|
|
@"MIDI-PAGE(?<page>\d+)-CTRL-C(?<chan>\d+)P(?<param>\d+)",
|
|
System.Text.RegularExpressions.RegexOptions.Compiled);
|
|
static System.Text.RegularExpressions.Regex regexPbEventID = new System.Text.RegularExpressions.Regex (
|
|
@"MIDI-PAGE(?<page>\d+)-PB-C(?<chan>\d+)",
|
|
System.Text.RegularExpressions.RegexOptions.Compiled);
|
|
static System.Text.RegularExpressions.Regex regexNoteEventID = new System.Text.RegularExpressions.Regex (
|
|
@"MIDI-PAGE(?<page>\d+)-NOTE-C(?<chan>\d+)N(?<note>\d+)",
|
|
System.Text.RegularExpressions.RegexOptions.Compiled);
|
|
|
|
string GetDescription (string eventId)
|
|
{
|
|
if (!eventlist.ContainsKey (eventId))
|
|
return null;
|
|
|
|
var res = regexCtrlEventID.Match (eventId);
|
|
if (res.Success) {
|
|
uint page = uint.Parse (res.Groups ["page"].Value);
|
|
byte chan = byte.Parse (res.Groups ["chan"].Value);
|
|
uint param = uint.Parse (res.Groups ["param"].Value);
|
|
return string.Format ("Page {2} => Control-Change C({0}) Param-{1}", chan + 1, param, page);
|
|
}
|
|
res = regexPbEventID.Match (eventId);
|
|
if (res.Success) {
|
|
uint page = uint.Parse (res.Groups ["page"].Value);
|
|
byte chan = byte.Parse (res.Groups ["chan"].Value);
|
|
return string.Format ("Page {1} => PitchBend C({0})", chan + 1, page);
|
|
}
|
|
res = regexNoteEventID.Match (eventId);
|
|
if (res.Success) {
|
|
uint page = uint.Parse (res.Groups ["page"].Value);
|
|
byte chan = byte.Parse (res.Groups ["chan"].Value);
|
|
byte note = byte.Parse (res.Groups ["note"].Value);
|
|
return string.Format ("Page {2} => Note C({0}) Note-{1}", chan + 1, note, page);
|
|
}
|
|
return eventId;
|
|
}
|
|
|
|
IFeedbackInfo IEventProvider.GetFeedbackInfo (string eventId)
|
|
{
|
|
if (!eventlist.ContainsKey (eventId))
|
|
return null;
|
|
|
|
return new midifeedbackinfo (this, eventlist [eventId]);
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region IDisposable implementation
|
|
|
|
bool disposed = false;
|
|
|
|
~MidiEventProvider ()
|
|
{
|
|
Dispose ();
|
|
}
|
|
|
|
public void Dispose ()
|
|
{
|
|
if (disposed)
|
|
return;
|
|
disposed = true;
|
|
//AlsaSeqLib.Close();
|
|
AlsaSeqLib.DeletePort(eventmidiport);
|
|
}
|
|
#endregion
|
|
|
|
public void Save (System.Xml.XmlElement parent)
|
|
{
|
|
System.Xml.XmlElement el = parent.OwnerDocument.CreateElement ("Midi");
|
|
parent.AppendChild (el);
|
|
el.SetAttribute ("maxpage", maxpage.ToString ());
|
|
el.SetAttribute ("pageUpCC", pageUpCC.ToString ());
|
|
el.SetAttribute ("pageDownCC", pageDownCC.ToString ());
|
|
el.SetAttribute ("fourteenbits", use14b?"true":"false");
|
|
el.SetAttribute ("max14b", max14bValue.ToString ());
|
|
|
|
System.Xml.XmlElement xmlEl;
|
|
foreach (MidiDevice dev in knowndevices.Values) {
|
|
el.AppendChild (xmlEl = parent.OwnerDocument.CreateElement ("MidiDev"));
|
|
xmlEl.SetAttribute ("name", dev.Name);
|
|
xmlEl.SetAttribute ("feedback", dev.HasFeedback.ToString ());
|
|
}
|
|
|
|
foreach (byte ch in unpaginatedchannels) {
|
|
el.AppendChild (xmlEl = parent.OwnerDocument.CreateElement ("UPC"));
|
|
xmlEl.SetAttribute ("ch", ch.ToString ());
|
|
}
|
|
|
|
}
|
|
|
|
public void Load (System.Xml.XmlElement el)
|
|
{
|
|
if (el == null)
|
|
return;
|
|
maxpage = uint.Parse (el.TryGetAttribute ("maxpage", "8"));
|
|
pageUpCC = uint.Parse (el.TryGetAttribute ("pageUpCC", "127"));
|
|
pageDownCC = uint.Parse (el.TryGetAttribute ("pageDownCC", "126"));
|
|
use14b = el.TryGetAttribute ("fourteenbits", string.Empty).Equals ("true");
|
|
max14bValue = int.Parse (el.TryGetAttribute ("max14b", "255"));
|
|
|
|
foreach (var xd in el.GetElementsByTagName("MidiDev")) {
|
|
System.Xml.XmlElement xdev = xd as System.Xml.XmlElement;
|
|
string name = xdev.GetAttribute ("name");
|
|
if (!knowndevices.ContainsKey (name))
|
|
knowndevices.Add (name, new MidiDevice (name));
|
|
knowndevices [name].HasFeedback = bool.Parse (xdev.TryGetAttribute ("feedback", "false"));
|
|
}
|
|
unpaginatedchannels.Clear ();
|
|
foreach (var xu in el.GetElementsByTagName("UPC")) {
|
|
System.Xml.XmlElement xupc = xu as System.Xml.XmlElement;
|
|
unpaginatedchannels.Add (byte.Parse (xupc.GetAttribute ("ch")));
|
|
}
|
|
|
|
AutoConnect ();
|
|
}
|
|
|
|
|
|
class internalEventDesc
|
|
{
|
|
|
|
readonly string internalName;
|
|
bool bound = false;
|
|
readonly uint page;
|
|
readonly int midiEvCode;
|
|
|
|
readonly midiFeedbackSender fbSender;
|
|
|
|
public bool Bound {
|
|
get {
|
|
return bound;
|
|
}
|
|
set {
|
|
bound = value;
|
|
}
|
|
}
|
|
|
|
public string InternalName {
|
|
get {
|
|
return internalName;
|
|
}
|
|
}
|
|
|
|
public uint Page {
|
|
get {
|
|
return page;
|
|
}
|
|
}
|
|
|
|
public int MidiEvCode {
|
|
get {
|
|
return midiEvCode;
|
|
}
|
|
}
|
|
|
|
public internalEventDesc (string _id, uint _page, int _evHCode, midiFeedbackSender _evsender)
|
|
{
|
|
internalName = _id;
|
|
page = _page;
|
|
midiEvCode = _evHCode;
|
|
fbSender = _evsender;
|
|
}
|
|
|
|
int lastknownvalue = -1;
|
|
|
|
public int LastKnownValue {
|
|
get {
|
|
return lastknownvalue;
|
|
}
|
|
set {
|
|
lastknownvalue = value;
|
|
}
|
|
}
|
|
|
|
bool nofbflag=false;
|
|
|
|
public void SendFeedback ()
|
|
{
|
|
if(fbSender !=null && !nofbflag)
|
|
fbSender.SendFeedback (lastknownvalue);
|
|
}
|
|
|
|
public void CallEvent (EventManagerCallback callback, EventData evData)
|
|
{
|
|
nofbflag = true;
|
|
callback (evData);
|
|
nofbflag = false;
|
|
}
|
|
}
|
|
|
|
abstract class midiFeedbackSender {
|
|
public abstract void SendFeedback (int value);
|
|
}
|
|
|
|
class midiCCFbSender : midiFeedbackSender {
|
|
readonly MidiEventProvider prov;
|
|
|
|
AlsaSeqLib.snd_seq_event_t ev;
|
|
AlsaSeqLib.snd_seq_event_t ev2;
|
|
|
|
public midiCCFbSender (MidiEventProvider _prov, byte _chan, uint _param){
|
|
prov = _prov;
|
|
ev = new AlsaSeqLib.snd_seq_event_t();
|
|
ev.type = AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_CONTROLLER;
|
|
ev.data_ev_ctrl.channel = _chan;
|
|
ev.data_ev_ctrl.param = _param;
|
|
ev2 = ev;
|
|
ev2.data_ev_ctrl.param += 32;
|
|
}
|
|
public override void SendFeedback (int value)
|
|
{
|
|
if (prov.use14b && ev.data_ev_ctrl.param <32 ) {
|
|
value = value * prov.max14bValue / 255;
|
|
ev.data_ev_ctrl.value = value >> 7;
|
|
ev2.data_ev_ctrl.value = value & 0xFF;
|
|
prov.SendEvent (ev);
|
|
prov.SendEvent (ev2);
|
|
} else {
|
|
ev.data_ev_ctrl.value = value * 127 / 255;
|
|
prov.SendEvent (ev);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
class midiPBFbSender : midiFeedbackSender {
|
|
readonly MidiEventProvider prov;
|
|
|
|
AlsaSeqLib.snd_seq_event_t ev;
|
|
|
|
public midiPBFbSender (MidiEventProvider _prov, byte _chan){
|
|
prov = _prov;
|
|
ev = new AlsaSeqLib.snd_seq_event_t();
|
|
ev.type = AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_PITCHBEND;
|
|
ev.data_ev_ctrl.channel = _chan;
|
|
|
|
}
|
|
public override void SendFeedback (int value)
|
|
{ // value = ((evS.data_ev_ctrl.value + 7000) * 255 / 14000);
|
|
|
|
ev.data_ev_ctrl.value = value * 14000 / 255 - 7000;
|
|
prov.SendEvent (ev);
|
|
}
|
|
}
|
|
|
|
class midifeedbackinfo : IFeedbackInfo
|
|
{
|
|
MidiEventProvider prov;
|
|
readonly internalEventDesc iev;
|
|
|
|
public midifeedbackinfo (MidiEventProvider _prov, internalEventDesc _iev)
|
|
{
|
|
prov = _prov;
|
|
iev = _iev;
|
|
}
|
|
|
|
#region IFeedbackInfo implementation
|
|
bool IFeedbackInfo.FeedBack (byte data)
|
|
{
|
|
|
|
iev.LastKnownValue = data;
|
|
|
|
if (prov.CurrentPage == iev.Page || iev.Page == 0) {
|
|
iev.SendFeedback();
|
|
foreach (int src in prov.feedbacksources) {
|
|
int lnvk = CombineHash (src, iev.MidiEvCode);
|
|
if (iev.LastKnownValue != -1)
|
|
prov.lastValueOfSrc [lnvk] = (byte)iev.LastKnownValue;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
public class MidiDevice
|
|
{
|
|
string name;
|
|
|
|
public string Name { get { return name; } }
|
|
|
|
public bool HasFeedback { get; set; }
|
|
|
|
readonly List<int> connected = new List<int> ();
|
|
|
|
public List<int> ConnectedPorts {
|
|
get{ return connected;}
|
|
}
|
|
|
|
public MidiDevice (string _name)
|
|
{
|
|
name = _name;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|