Last active
April 17, 2017 13:09
-
-
Save GregaMohorko/59b9ba2288d2e5c1af5e8d7152a0462a to your computer and use it in GitHub Desktop.
Realtime telephone keypad decoder.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading.Tasks; | |
namespace GM.SP | |
{ | |
/// <summary> | |
/// https://en.wikipedia.org/wiki/Telephone_keypad | |
/// </summary> | |
public class TelephoneKeypadDecoder : IDisposable | |
{ | |
/// <summary> | |
/// Threshold after which the multi-tap feature is reset. | |
/// Value is in ms. | |
/// https://en.wikipedia.org/wiki/Multi-tap | |
/// </summary> | |
public const int MULTITAP_THRESHOLD = 800; | |
/// <summary> | |
/// Occurs when a new key is decoded. | |
/// </summary> | |
public event EventHandler<NewKeyEventArgs> NewKey; | |
private volatile int lastNumber; | |
private char lastKey; | |
private int lastKeyTime; | |
private volatile uint lastKeyCount; | |
private volatile bool wasLastKeyDefinite; | |
private int multitapCount; | |
private volatile bool isDisposed; | |
public TelephoneKeypadDecoder() | |
{ | |
// something that doesn't exist on the keypad | |
lastNumber = -1; | |
lastKey = '-'; | |
lastKeyTime = GetTime(); | |
lastKeyCount = 0; | |
wasLastKeyDefinite = true; | |
multitapCount = 0; | |
} | |
public void Dispose() | |
{ | |
isDisposed = true; | |
} | |
public void NewNumber(int number) | |
{ | |
uint myID = ++lastKeyCount; | |
// check multi-tap | |
{ | |
int time = GetTime(); | |
if(number != lastNumber) { | |
multitapCount = 0; | |
if(!wasLastKeyDefinite) { | |
// we have to push previous key as definite | |
NewKeyEventArgs definiteKeyArgs = new NewKeyEventArgs(); | |
definiteKeyArgs.Key = lastKey; | |
definiteKeyArgs.IsDefinite = true; | |
NewKey?.Invoke(this, definiteKeyArgs); | |
} | |
}else { | |
if(time - lastKeyTime < MULTITAP_THRESHOLD) | |
// the same number was pressed quicker than the multitap threshold | |
++multitapCount; | |
else | |
multitapCount = 0; | |
} | |
lastKeyTime = time; | |
} | |
lastNumber = number; | |
// decode key | |
char key = DecodeKey(number, multitapCount); | |
lastKey = key; | |
// wait for multitap threshold amount of time | |
Task.Delay(MULTITAP_THRESHOLD).ContinueWith(delegate | |
{ | |
if(isDisposed) | |
return; | |
if(myID != lastKeyCount) | |
// new numbers were present after this one | |
return; | |
lastNumber = -1; | |
// invoke this key as the definite one | |
wasLastKeyDefinite = true; | |
NewKeyEventArgs definiteKeyArgs = new NewKeyEventArgs(); | |
definiteKeyArgs.Key = key; | |
definiteKeyArgs.IsDefinite = true; | |
NewKey?.Invoke(this, definiteKeyArgs); | |
}); | |
// invoke the key as indefinite, because the definite one will be pushed later | |
wasLastKeyDefinite = false; | |
NewKeyEventArgs newKeyArgs = new NewKeyEventArgs(); | |
newKeyArgs.Key = key; | |
newKeyArgs.IsDefinite = false; | |
NewKey?.Invoke(this, newKeyArgs); | |
} | |
/// <summary> | |
/// https://en.wikipedia.org/wiki/Telephone_keypad#Layout | |
/// </summary> | |
private char DecodeKey(int number,int multitap) | |
{ | |
switch(number) { | |
case 0: | |
case 1: | |
// use 0 and 1 for space | |
return ' '; | |
case 2: | |
switch(multitapCount % 3) { | |
case 0: | |
return 'A'; | |
case 1: | |
return 'B'; | |
case 2: | |
return 'C'; | |
} | |
break; | |
case 3: | |
switch(multitapCount % 3) { | |
case 0: | |
return 'D'; | |
case 1: | |
return 'E'; | |
case 2: | |
return 'F'; | |
} | |
break; | |
case 4: | |
switch(multitapCount % 3) { | |
case 0: | |
return 'G'; | |
case 1: | |
return 'H'; | |
case 2: | |
return 'I'; | |
} | |
break; | |
case 5: | |
switch(multitapCount % 3) { | |
case 0: | |
return 'J'; | |
case 1: | |
return 'K'; | |
case 2: | |
return 'L'; | |
} | |
break; | |
case 6: | |
switch(multitapCount % 3) { | |
case 0: | |
return 'M'; | |
case 1: | |
return 'N'; | |
case 2: | |
return 'O'; | |
} | |
break; | |
case 7: | |
switch(multitapCount % 4) { | |
case 0: | |
return 'P'; | |
case 1: | |
return 'Q'; | |
case 2: | |
return 'R'; | |
case 3: | |
return 'S'; | |
} | |
break; | |
case 8: | |
switch(multitapCount % 3) { | |
case 0: | |
return 'T'; | |
case 1: | |
return 'U'; | |
case 2: | |
return 'V'; | |
} | |
break; | |
case 9: | |
switch(multitapCount % 4) { | |
case 0: | |
return 'W'; | |
case 1: | |
return 'X'; | |
case 2: | |
return 'Y'; | |
case 3: | |
return 'Z'; | |
} | |
break; | |
} | |
throw new NotImplementedException("Wrong number and/or multitap."); | |
} | |
private int GetTime() | |
{ | |
return DateTime.UtcNow.Millisecond; | |
} | |
} | |
public class NewKeyEventArgs : EventArgs | |
{ | |
public char Key; | |
/// <summary> | |
/// If false, it means that this key is only a part of a multi-tap sequence and is therefore not yet definite. | |
/// </summary> | |
public bool IsDefinite; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment