/* 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 . */ using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Linq; namespace DMX2 { public class MidiEventProvider : IEventProvider { class internalEvent { public string InternalName{ get; set; } bool bound=false; public bool Bound { get { return bound; } set { bound = value; } } public internalEvent(string _id) { InternalName=_id; } int[] sources=null; public IEnumerable Sources { get { if(sources == null || sources.Length!=lastknownvalues.Count) sources = lastknownvalues.Keys.ToArray(); return sources; } } readonly Dictionary lastknownvalues = new Dictionary(); public Dictionary LastKnownValues { get { return lastknownvalues; } } AlsaSeqLib.snd_seq_event_t storedevent; public AlsaSeqLib.snd_seq_event_t StoredEvent { get { return storedevent; } set { storedevent = value; } } } abstract class feedbackinfo : IFeedbackInfo { MidiEventProvider prov; protected AlsaSeqLib.snd_seq_event_t ev; string eventId; int page; public feedbackinfo(MidiEventProvider _prov, string _eventId, int _page){ prov = _prov; eventId = _eventId; page = _page; } protected abstract bool UpdateEvent(byte data); #region IFeedbackInfo implementation bool IFeedbackInfo.FeedBack (byte data) { if (!prov.eventlist.ContainsKey (eventId)) return false; bool update = UpdateEvent (data); prov.eventlist[eventId].StoredEvent = ev; if(!update) return true; foreach (var src in prov.eventlist[eventId].Sources) { if(prov.HasFeedback(src)) { prov.eventlist[eventId].LastKnownValues[src]=data; } } if(prov.CurrentPage == page || page == 0) prov.SendEvent (ev); return true; } #endregion } class ctrlfeedbackinfo : feedbackinfo { public ctrlfeedbackinfo(MidiEventProvider _prov, string _eventId, int _page, byte channel,uint param) :base(_prov,_eventId,_page) { ev = new AlsaSeqLib.snd_seq_event_t(); ev.data_ev_ctrl.channel = channel; ev.data_ev_ctrl.param= param; ev.type = AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_CONTROLLER; } protected override bool UpdateEvent (byte data) { int newvalue= (int)data * 127 / 255; if(newvalue==ev.data_ev_ctrl.value) return false; ev.data_ev_ctrl.value = newvalue; return true; } } class pitchbendfeedbackinfo : feedbackinfo { public pitchbendfeedbackinfo(MidiEventProvider _prov, string _eventId,int _page, byte channel) :base(_prov,_eventId,_page) { ev = new AlsaSeqLib.snd_seq_event_t(); ev.data_ev_ctrl.channel = channel; ev.type = AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_PITCHBEND; } protected override bool UpdateEvent (byte data) { int newvalue = (int)data * 16000 / 255-8000; if(newvalue==ev.data_ev_ctrl.value) return false; ev.data_ev_ctrl.value = newvalue; return true; } } public class MidiDev{ string name; public string Name {get{return name;} } public bool HasFeedback { get; set; } readonly List connected = new List(); public List ConnectedPorts { get{return connected;} } public MidiDev(string _name) { name=_name; } } readonly Dictionary eventlist = new Dictionary(); readonly Dictionary knowndevices = new Dictionary(); readonly List feedbacksources = new List(); readonly List unpaginatedchannels = new List(); EventData last; internalEvent levent=null; bool connected=false; int page=1; public int CurrentPage { get { return page; } set { if(value<1 || value > 255) return; page = value; Refresh(); Console.WriteLine(page); } } public List UnpaginatedChannels { get { return unpaginatedchannels; } } public Dictionary KnownDevices { get { return knowndevices; } } public MidiEventProvider (EventManager manager) { manager.RegisterProvider (this); AlsaSeqLib.Init (); AlsaSeqLib.ConnectFrom(AlsaSeqLib.SND_SEQ_CLIENT_SYSTEM, AlsaSeqLib.SND_SEQ_PORT_SYSTEM_ANNOUNCE); MidiDev dev = new MidiDev("VMPK Input:VMPK Input"); dev.HasFeedback = true; knowndevices.Add(dev.Name,dev); dev = new MidiDev("VMPK Output:VMPK Output"); dev.HasFeedback = true; knowndevices.Add(dev.Name,dev); foreach (var cli in AlsaSeqLib.EnumClients()) { foreach(var p in cli.Ports){ PortDetected(cli,p); } } unpaginatedchannels.Add((byte)0); } 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 string fullportname = cli.Name + ':' + p.Name; Console.WriteLine(fullportname); if(knowndevices.ContainsKey(fullportname)){ AlsaSeqLib.Connect(p); int srcid = p.ClientId <<8 + p.PortId; knowndevices[fullportname].ConnectedPorts.Add(srcid); if(knowndevices[fullportname].HasFeedback) feedbacksources.Add(srcid); } } protected bool HasFeedback (int source) { return feedbacksources.Contains(source); } public void SendEvent (AlsaSeqLib.snd_seq_event_t ev) { AlsaSeqLib.SendEventToSubscribers(ev); } public void Refresh () { string pageStr = string.Format("PAGE{0}",page); foreach (var ievent in eventlist.Values) { if (ievent.InternalName.Contains(pageStr)){ SendEvent(ievent.StoredEvent); } } } #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)) eventlist.Add(eventId, new internalEvent(eventId)); 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) { if(!connected) return null; // Si pas encore recu d'evenements => pas de menu 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; lmenu.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 sortedKeys = eventlist.Keys.ToList(); // On recupere des IDs sortedKeys.Sort(); // et on les trie foreach ( string key in sortedKeys ) { internalEvent 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; int evpage; // Tant qu'il y des evenements midi en attente while (AlsaSeqLib.GetEvent(out evS)) { 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 connected = true; continue; case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_CONTROLLER: if(evS.data_ev_ctrl.param==127 && value>0){ CurrentPage++; continue; } if(evS.data_ev_ctrl.param==126 && value>0){ CurrentPage--; continue; } id = string.Format("CTRL-C{0}P{1}",evS.data_ev_ctrl.channel,evS.data_ev_ctrl.param); channel = evS.data_ev_ctrl.channel; value = 255 * evS.data_ev_ctrl.value / 127; // Conversion {0,127} => {0,255} 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 + 8000) *255 / 16000); if(value<0) value =0; if(value>255) value = 255; break; case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_CLOCK: case AlsaSeqLib.snd_seq_event_type_t.SND_SEQ_EVENT_SENSING: 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; //TODO : Regarder si d'autres controles interessants. default: id= null; #if DEBUG Info.Publish(string.Format ("event {0}", evS.type) ); // On affiche les evenements inconnus #endif continue; } connected=true; if(id!=null) { if(channel == 255 || unpaginatedchannels.Contains(channel)) evpage= 0; else evpage= page; id = string.Format("MIDI-PAGE{0}-{1}",evpage,id); if(!eventlist.ContainsKey(id)) eventlist.Add(id,new internalEvent(id)); levent= eventlist[id]; //Dernier Evenement recu conserve pour menu int srcid = evS.source.client <<8 + evS.source.port; if(!eventlist[id].LastKnownValues.ContainsKey(srcid)) eventlist[id].LastKnownValues[srcid] = (byte)value; EventData evData = new EventData(); evData.id = id; evData.value = (byte)value; evData.prev_value = eventlist[id].LastKnownValues[srcid]; eventlist[id].LastKnownValues[srcid] = (byte)value; if(evData.Equals(last)) continue; last = evData; if(eventlist[id].Bound) { callback(evData); } } } } string IEventProvider.MenuName { get { return "Midi"; } } //TODO gerer pages et feeddback static System.Text.RegularExpressions.Regex regexCtrlEventID = new System.Text.RegularExpressions.Regex( @"MIDI-PAGE(?\d+)-CTRL-C(?\d+)P(?\d+)", System.Text.RegularExpressions.RegexOptions.Compiled); static System.Text.RegularExpressions.Regex regexPbEventID = new System.Text.RegularExpressions.Regex( @"MIDI-PAGE(?\d+)-PB-C(?\d+)", System.Text.RegularExpressions.RegexOptions.Compiled); string GetDescription (string eventId) { return eventId; } IFeedbackInfo IEventProvider.GetFeedbackInfo (string eventId) { var res = regexCtrlEventID.Match (eventId); if (res.Success) { Console.WriteLine("Succes"); byte chan = byte.Parse (res.Groups ["chan"].Value); uint param = uint.Parse (res.Groups ["param"].Value); return new ctrlfeedbackinfo (this, eventId, page, chan, param); } res = regexPbEventID.Match (eventId); if (res.Success) { Console.WriteLine("Succes"); byte chan = byte.Parse (res.Groups ["chan"].Value); return new pitchbendfeedbackinfo (this, eventId,page, chan); } return null; } #endregion } }