loupiottes/DMX-2.0/MidiEventProvider.cs

481 lines
13 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;
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<int> Sources {
get {
if(sources == null || sources.Length!=lastknownvalues.Count) sources = lastknownvalues.Keys.ToArray();
return sources;
}
}
readonly Dictionary<int,byte> lastknownvalues = new Dictionary<int, byte>();
public Dictionary<int, byte> 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<int> connected = new List<int>();
public List<int> ConnectedPorts {
get{return connected;}
}
public MidiDev(string _name)
{
name=_name;
}
}
readonly Dictionary<string,internalEvent> eventlist = new Dictionary<string, internalEvent>();
readonly Dictionary<string,MidiDev> knowndevices = new Dictionary<string,MidiDev>();
readonly List<int> feedbacksources = new List<int>();
readonly List<byte> unpaginatedchannels = new List<byte>();
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<byte> UnpaginatedChannels {
get {
return unpaginatedchannels;
}
}
public Dictionary<string,MidiDev> 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);
dev = new MidiDev("BCF2000:BCF2000 MIDI 1");
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<string> 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(?<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);
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
}
}