Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Realtime telephone keypad decoder.
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
You can’t perform that action at this time.