513 lines
13 KiB
C#
513 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 {
|
|
|
|
readonly string internalName;
|
|
bool bound=false;
|
|
readonly int page;
|
|
readonly int midiEvCode;
|
|
|
|
public bool Bound {
|
|
get {
|
|
return bound;
|
|
}
|
|
set {
|
|
bound = value;
|
|
}
|
|
}
|
|
|
|
public string InternalName {
|
|
get {
|
|
return internalName;
|
|
}
|
|
}
|
|
|
|
public int Page {
|
|
get {
|
|
return page;
|
|
}
|
|
}
|
|
|
|
public int MidiEvCode {
|
|
get {
|
|
return midiEvCode;
|
|
}
|
|
}
|
|
|
|
|
|
public internalEvent(string _id, int _page, int _evHCode)
|
|
{
|
|
internalName=_id;
|
|
page=_page;
|
|
midiEvCode = _evHCode;
|
|
}
|
|
|
|
byte lastknownvalue;
|
|
|
|
public byte LastKnownValue {
|
|
get {
|
|
return lastknownvalue;
|
|
}
|
|
set {
|
|
lastknownvalue = value;
|
|
}
|
|
}
|
|
|
|
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;
|
|
readonly internalEvent iev;
|
|
protected AlsaSeqLib.snd_seq_event_t ev;
|
|
|
|
|
|
public feedbackinfo(MidiEventProvider _prov, internalEvent _iev){
|
|
prov = _prov;
|
|
iev=_iev;
|
|
}
|
|
|
|
protected abstract bool UpdateEvent(byte data);
|
|
|
|
#region IFeedbackInfo implementation
|
|
bool IFeedbackInfo.FeedBack (byte data)
|
|
{
|
|
|
|
bool update = UpdateEvent (data);
|
|
iev.StoredEvent = ev;
|
|
if (!update) return true;
|
|
|
|
iev.LastKnownValue = data;
|
|
|
|
if (prov.CurrentPage == iev.Page || iev.Page == 0) {
|
|
prov.SendEvent (ev);
|
|
foreach (int srcid in prov.feedbacksources) {
|
|
int lnvk = srcid ^ iev.MidiEvCode;
|
|
prov.lastKnownValues[lnvk] = data;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
class ctrlfeedbackinfo : feedbackinfo {
|
|
public ctrlfeedbackinfo(MidiEventProvider _prov, internalEvent _iev, byte channel,uint param)
|
|
:base(_prov,_iev)
|
|
{
|
|
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, internalEvent _iev, byte channel)
|
|
:base(_prov,_iev)
|
|
{
|
|
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>();
|
|
readonly Dictionary<int,byte> lastKnownValues = new Dictionary<int, 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 ()
|
|
{
|
|
foreach (var ievent in eventlist.Values) {
|
|
if (ievent.Page == page){
|
|
SendEvent(ievent.StoredEvent);
|
|
foreach(int src in feedbacksources){
|
|
int lnvk = src ^ ievent.MidiEvCode;
|
|
lastKnownValues[lnvk] = ievent.LastKnownValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#region IEventProvider implementation
|
|
|
|
|
|
static System.Text.RegularExpressions.Regex regexEventID = new System.Text.RegularExpressions.Regex(
|
|
@"MIDI-PAGE(?<page>\d+)-(?<id>.+)",
|
|
System.Text.RegularExpressions.RegexOptions.Compiled);
|
|
|
|
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)) {
|
|
var res = regexEventID.Match (eventId);
|
|
if (!res.Success) return false;
|
|
int _page = byte.Parse (res.Groups ["page"].Value);
|
|
int _evHC = res.Groups["id"].Value.GetHashCode();
|
|
eventlist.Add (eventId, new internalEvent (eventId,_page,_evHC ));
|
|
}
|
|
|
|
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 && evS.data_ev_ctrl.value >0){
|
|
CurrentPage++; continue;
|
|
}
|
|
|
|
if(evS.data_ev_ctrl.param==126 && evS.data_ev_ctrl.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)
|
|
{
|
|
|
|
int evHC = id.GetHashCode();
|
|
|
|
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,page,evHC));
|
|
levent= eventlist[id]; //Dernier Evenement recu conserve pour menu
|
|
|
|
int srcid = evS.source.client <<8 + evS.source.port;
|
|
int lnvk = srcid ^ evHC;
|
|
if(!lastKnownValues.ContainsKey(lnvk))
|
|
lastKnownValues[lnvk]= (byte)value;
|
|
|
|
EventData evData = new EventData();
|
|
evData.id = id;
|
|
evData.value = (byte)value;
|
|
evData.prev_value = lastKnownValues[lnvk];
|
|
|
|
lastKnownValues[lnvk] = (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)
|
|
{
|
|
if(!eventlist.ContainsKey(eventId)) return null;
|
|
|
|
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, eventlist[eventId],chan, param);
|
|
}
|
|
res = regexPbEventID.Match (eventId);
|
|
if (res.Success) {
|
|
Console.WriteLine("Succes");
|
|
byte chan = byte.Parse (res.Groups ["chan"].Value);
|
|
|
|
return new pitchbendfeedbackinfo (this, eventlist[eventId], chan);
|
|
}
|
|
return null;
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|