GithubHelp home page GithubHelp logo

bert2 / dtmfdetection Goto Github PK

View Code? Open in Web Editor NEW
42.0 4.0 19.0 6.46 MB

C# implementation of the Goertzel algorithm for DTMF tone (a.k.a. Touch-Tone) detection and localization in audio data. Includes wrappers and extensions for NAudio.

License: MIT License

C# 95.16% PowerShell 4.84%
dtmf dtmf-detector dtmf-decoder dtmfgenerator goertzel-algorithm naudio touch-tone

dtmfdetection's Introduction

DtmfDetection & DtmfDetection.NAudio

build tests coverage nuget package nuget downloads last commit

Implementation of the Goertzel algorithm for the detection of DTMF tones (aka touch tones) in audio data.

package use case
DtmfDetection Use this package if you are only working with raw PCM data (i.e. arrays of floats).
DtmfDetection.NAudio Integrates with NAudio to detect DTMF tones in audio files and audio streams (e.g. mic-in or the current audio output).

Quick start

In case DtmfDetection is not detecting any or only some of the DTMF tones in your audio data, have look at the troubleshooting section first.

With NAudio

How to detect and print DTMF changes (DTMF tone starting or stopping) in an mp3 file:

using System;
using DtmfDetection.NAudio;
using NAudio.Wave;

class Program {
    static void Main() {
        using var audioFile = new AudioFileReader("long_dtmf_tones.mp3");
        var dtmfs = audioFile.DtmfChanges();
        foreach (var dtmf in dtmfs) Console.WriteLine(dtmf);
    }
}
Output

1 started @ 00:00:02.7675736 (ch: 0)
1 stopped @ 00:00:05.5607029 (ch: 0)
2 started @ 00:00:06.7138321 (ch: 0)
2 stopped @ 00:00:06.8675736 (ch: 0)
3 started @ 00:00:07.3031972 (ch: 0)
3 stopped @ 00:00:07.4313378 (ch: 0)
4 started @ 00:00:08.2000680 (ch: 0)
4 stopped @ 00:00:10.5319501 (ch: 0)
5 started @ 00:00:12.0950793 (ch: 0)
5 stopped @ 00:00:12.2744444 (ch: 0)
6 started @ 00:00:12.7357142 (ch: 0)
6 stopped @ 00:00:12.8125850 (ch: 0)
7 started @ 00:00:14.5038321 (ch: 0)
7 stopped @ 00:00:14.5294557 (ch: 0)
7 started @ 00:00:14.5550793 (ch: 0)
7 stopped @ 00:00:16.8357142 (ch: 0)
8 started @ 00:00:17.6813378 (ch: 0)
8 stopped @ 00:00:17.7582086 (ch: 0)
9 started @ 00:00:18.4500680 (ch: 0)
9 stopped @ 00:00:18.5269614 (ch: 0)
# started @ 00:00:19.1163265 (ch: 0)
# stopped @ 00:00:19.1419501 (ch: 0)
# started @ 00:00:19.1675736 (ch: 0)
# stopped @ 00:00:19.3469614 (ch: 0)
0 started @ 00:00:19.8338321 (ch: 0)
0 stopped @ 00:00:19.8850793 (ch: 0)
* started @ 00:00:20.4744444 (ch: 0)
* stopped @ 00:00:20.6025850 (ch: 0)
1 started @ 00:00:22.0119501 (ch: 0)
1 stopped @ 00:00:23.7544444 (ch: 0)

How to detect and print multi-channel DTMF changes in a wav file while also merging the start and stop of each DTMF tone into a single data structure:

using System;
using DtmfDetection;
using DtmfDetection.NAudio;
using NAudio.Wave;

class Program {
    static void Main() {
        using var audioFile = new AudioFileReader("stereo_dtmf_tones.wav");
        var dtmfs = audioFile.DtmfChanges(forceMono: false).ToDtmfTones();
        foreach (var dtmf in dtmfs) Console.WriteLine(dtmf);
    }
}
Output

1 @ 00:00:00 (len: 00:00:00.9994557, ch: 0)
2 @ 00:00:01.9988208 (len: 00:00:00.9993878, ch: 1)
3 @ 00:00:03.9975736 (len: 00:00:01.9987529, ch: 0)
4 @ 00:00:04.9969614 (len: 00:00:01.9987528, ch: 1)
5 @ 00:00:07.9950793 (len: 00:00:00.9993651, ch: 0)
6 @ 00:00:07.9950793 (len: 00:00:00.9993651, ch: 1)
7 @ 00:00:09.9938321 (len: 00:00:02.9981180, ch: 0)
8 @ 00:00:11.0188208 (len: 00:00:00.9737642, ch: 1)
9 @ 00:00:14.0169614 (len: 00:00:00.9737415, ch: 0)
0 @ 00:00:15.0163265 (len: 00:00:00.9737415, ch: 0)

How to detect and print DTMF changes in audio output:

using System;
using DtmfDetection.NAudio;
using NAudio.CoreAudioApi;
using NAudio.Wave;

class Program {
    static void Main() {
        using var audioSource = new WasapiLoopbackCapture {
            ShareMode = AudioClientShareMode.Shared
        };

        using var analyzer = new BackgroundAnalyzer(audioSource);

        analyzer.OnDtmfDetected += dtmf => Console.WriteLine(dtmf);

        _ = Console.ReadKey(intercept: true);
    }
}

How to detect and print DTMF changes in microphone input while also lowering the detection threshold:

using System;
using DtmfDetection;
using DtmfDetection.NAudio;
using NAudio.Wave;

class Program {
    static void Main() {
        using var audioSource = new WaveInEvent {
            WaveFormat = new WaveFormat(Config.Default.SampleRate, bits: 32, channels: 1)
        };

        using var analyzer = new BackgroundAnalyzer(
            audioSource,
            Config.Default.WithThreshold(10));

        analyzer.OnDtmfDetected += dtmf => Console.WriteLine(dtmf);

        _ = Console.ReadKey(intercept: true);
    }
}

Without NAudio

How to detect and print DTMF tones in an array of PCM samples:

using System;
using System.Linq;
using DtmfDetection;

using static DtmfDetection.DtmfGenerator;

class Program {
    static void Main() {
        var samples = GenerateStereoSamples();
        foreach (var dtmf in samples.DtmfChanges(channels: 2))
            Console.WriteLine(dtmf);
    }

    // `DtmfDetection.DtmfGenerator` has helpers for generating DTMF tones.
    static float[] GenerateStereoSamples() =>
        Stereo(
            left: Generate(PhoneKey.Star),
            right: Concat(Mark(PhoneKey.One, ms: 40), Space(ms: 20), Mark(PhoneKey.Two, ms: 40)))
        .Take(NumSamples(milliSeconds: 40 + 20 + 40, channels: 2))
        .ToArray();
}
Output

* started @ 00:00:00 (ch: 0)
1 started @ 00:00:00 (ch: 1)
1 stopped @ 00:00:00.0000026 (ch: 1)
2 started @ 00:00:00.0000051 (ch: 1)
* stopped @ 00:00:00.0000100 (ch: 0)
2 stopped @ 00:00:00.0000100 (ch: 1)

Pre-built example tool

TODO: deploy example tool to choco

DTMF tone localization accuracy

Be aware that this library cannot locate DTMF tones with 100% accuracy, because the detector analyzes the data in ~26 ms blocks with the default configuration. This block size determines the resolution of the localization and every DTMF tone starting position will be "rounded off" to the start of the nearest block.

For instance, if a DTMF tone starts at 35 ms into the audio, its calculated starting position will be around 26 ms, i.e. at the beginning of the second block.

A resolution of 26 ms might seem rather inaccurate relative to the typical duration of a DTMF tone (40 ms). However, keep in mind that DTMF analysis typically is about correctly detecting DTMF tones and not about accurately locating them.

Configuring the detector

The library is designed to be very configurable. Of course, each setting of the detector configuration can be changed. Additionally it is possible to replace any part of its logic with a custom implementation.

Adjusting the detection threshold

The detector's threshold value is probably the setting that needs to be tweaked most often. Depending on the audio source and quality, the threshold might have to be increased to reduce false positives or decreased to reduce false negatives.

Typical values are between 30 and 35 with enabled Goertzel response normalization and 100 to 115 without it. Its default value is 30.

Changing the threshold value is easy, because each of the three main entry points take an optional Config argument (defaulting to Config.Default):

  • List<DtmfChange> float[].DtmfChanges(int, int, Config?)
  • List<DtmfChange> WaveStream.DtmfChanges(bool, Config?)
  • BackgroundAnalyzer(IWaveIn, bool, Action<DtmfChange>?, Config?, IAnalyzer?)

Now, simply create your own Config instance and pass it to the entry point you want to use:

var mycfg = new Config(threshold: 20, sampleBlockSize: ..., ...);
var dmtfs = waveStream.DtmfChanges(config: mycfg);

Or you start off with the default config and adjust it with one of its builder methods:

var mycfg = Config.Default.WithThreshold(20);

Disabling Goertzel response normalization

As of version 1.0.0 the frequency response calculated with Goertzel algorithm will be normalized with the total energy of the input signal. This effectively makes the detector invariant against changes in the loudness of the signal with very little additional computational costs.

You can test this yourself with a simple example program that detects DTMF tones in your system's current audio output, but with disabled response normalization:

using System;
using DtmfDetection.NAudio;
using NAudio.CoreAudioApi;
using NAudio.Wave;

class Program {
    static void Main() {
        using var audioSource = new WasapiLoopbackCapture {
            ShareMode = AudioClientShareMode.Shared
        };

        using var analyzer = new BackgroundAnalyzer(
            audioSource,
            config: Config.Default.WithNormalizeResponse(false));

        analyzer.OnDtmfDetected += dtmf => Console.WriteLine(dtmf);

        _ = Console.ReadKey(intercept: true);
    }
}

Now play any of the test files and observe the program's output. If your playback volume is high enough, DTMF tones should be detected. Try to gradually decrease the volume and see that no more DTMF tones will be detected as soon as the Goertzel responses fall below the detection threshold. With enabled response normalization all DTMF tones should be detected regardless of the volume level.

I generally recommend to leave response normalization enabled, because loudness invariance ensures that DTMF tones are detected correctly in a wider range of scenarios. However, if you are analyzing audio signals that feature strong background noises, you might accomplish better detection results by disabling response normalization.

Just note that without response normalization the detection threshold has to be significantly increased and depends on the loudness of your signal. A good starting point is a value of 100.

Providing a custom source of sample data

Different kinds of sample data are fed to the analysis in a unified way using the ISamples interface. Currently there are three implementations of ISamples:

implementation package usage
AudioData DtmfDetection created from a float[]
AudioFile DtmfDetection.NAudio created from an NAudio WaveStream
AudioStream DtmfDetection.NAudio created from an NAudio IWaveIn

In case none of the above implementations suit your needs, you can implement the interface yourself and pass it directly to the Analyzer:

// Untested `ISamples` implementation for `System.IO.Stream`s.
public class MySamples : ISamples, IDisposable {
    private readonly BinaryReader reader;
    private long position;
    public int Channels => 1;
    public int SampleRate => 8000;
    public TimeSpan Position => new TimeSpan((long)Math.Round(position * 1000.0 / SampleRate));
    public MySamples(Stream samples) => this.reader = new BinaryReader(samples);
    public int Read(float[] buffer, int count) {
        var safeCount = Math.Min(count, reader.BaseStream.Length / sizeof(float) - position);
        for (var i = 0; i < safeCount; i++, position++)
            buffer[i] = reader.ReadSingle();
        return (int)safeCount;
    }
    public void Dispose() => reader.Dispose();
}

// ...

var mySamples = new MySamples(myStream);
var analyzer = Analyzer.Create(mySamples, Config.Default);
var dtmfs = new List<DtmfChange>();

while (analyzer.MoreSamplesAvailable)
    dtmfs.AddRange(analyzer.AnalyzeNextBlock());

Refer to the API reference of the ISamples interface for more details on how to implement it correctly.

Injecting a custom detector implementation

TODO: document how to

Other configuration options

TODO: document other options

Troubleshooting

None or not all expected DTMF tones are detected

You should first try to lower the detection threshold. The default config uses a value of 30 which might be too high for your audio data. In case you get false positives afterwards, try increasing the threshold again to find the "sweet spot". If possible try to de-noise the audio data.

You can also try to disable the Goertzel response normalization. Generally this is not recommended, but it might give better results in certain cases. Be aware that without normalization you need to ensure that the threshold is tuned to the loudness of your audio data. I.e. when your audio becomes louder, you should also increase the threshold and when it becomes quieter, you have to decrease it. You should also use a higher starting value for the threshold (around 100).

API reference

DtmfDetection: ./src/DtmfDetection/README.md

DtmfDetection.NAudio: ./src/DtmfDetection.NAudio/README.md

Changelog

1.2.2

  • refactor wait time estimation of the BackgroundAnalyzer to a less handcrafted solution

1.2.1

  • BackgroundAnalyzer ctor now also takes a handler for the OnDtmfDetected event

1.2.0

  • add XML documentation
  • generate API reference

1.1.0

DtmfDetection:

  • add extension method for analyzing float arrays
  • add helpers for generating DTMF tones

1.0.1

DtmfDetection:

  • remove unwanted dependencies from nuget package

1.0.0

  • upgrade to netstandard2.1
  • make implementation much more configurable
  • improve runtime performance by ~25%

DtmfDetection:

  • normalize Goertzel response with total signal energy for loudness invariance

DtmfDetection.NAudio:

  • update NAudio reference to 1.10.0
  • correctly calculate wait time until enough samples can be read when analyzing audio provided by a NAudio.Wave.BufferedWaveProvider stream

0.9.2

DtmfDetection.NAudio:

  • update NAudio reference to 1.8.4.0

0.9.1

DtmfDetection:

  • update to .NET framework 4.7
  • reduce memory footprint a little bit

DtmfDetection.NAudio:

  • update to .NET framework 4.7

0.9.0

DtmfDetection:

  • implement multi-channel support
  • fix short DTMF tones not being detected
  • adjust Goertzel algorithm implementation a bit

DtmfDetection.NAudio:

  • fix mono-conversion (average all channels)

TODO

  • finish README
  • continuous deployment of CLI tool to choco
  • add config options to CLI tool

dtmfdetection's People

Contributors

bert2 avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

dtmfdetection's Issues

Consuming too much processor.

I implemented the dll in my project. but only it consumes 20% of my processor.
Is there any way to improve this?
Thank you.

How to ensure the latest NAudio version is in use?

Hi @bert2 ,

I've been trying to upgrade NAudio package to the latest release (2.1.0) using dotnet add package NAudio --version 2.1.0 in the below three csproj:

unit.csproj
integration.csproj
dtmf-detector.csproj
DtmfDetection.NAudio.csproj

Is that sufficient to claim that DtmfDetection.NAudio is using the latest NAudio version ?

Incorrect keys at start of sequence

I'm using "pure" PCM 16 bit 8000Hz 100ms WAV files as a source.

If I transmit a "long" sequence of DTMF codes
e.g.
1234567890#*
The last digits are detected correctly (with durations of around 6 to 8 milliseconds)
But I usually get incorrect and/or extra digits at the start (with durations of 4 to 5 milliseconds)

For the sequence above for instance I just got
One key 0.0050005 (channel 0)
One key 0.0050005 (channel 0)
Two key 0.0040004 (channel 0)
One key 0.0050005 (channel 0)
Two key 0.0060006 (channel 0)
Three key 0.0070007 (channel 0)
Four key 0.0070007 (channel 0)
Five key 0.0070007 (channel 0)
Six key 0.0070007 (channel 0)
Seven key 0.0070007 (channel 0)
Eight key 0.0070007 (channel 0)
Nine key 0.0080008 (channel 0)
Zero key 0.0080008 (channel 0)
Star key 0.0080008 (channel 0)
Hash key 0.0080008 (channel 0)

Reversing the order (#0987654321), I first got a correct set, then got
9
87654321

Now admittedly, this is going over UDP out to a SIP server and back again, so I guess it's possible that I'm getting packet loss or out of order arrival, but is there any way to tell that? Or to adjust the minimum detection interval?

Is it possible to change the detection frequency?

Hi @bert2

Is it possible to change the detection frequency, or process by T seconds intervals ?

By default, the DTMF duration is 40ms. But sometimes it can be 60, 70, or even 80ms.

So the precision of the result depends on the number of detection per seconds.

Is there a way to modify it with DtmfDetection, or is it fixed?

Is it possible to use a named pipe (fifo) as input of NAudio?

Hi, @bert2

I'm working on a requirement where I'm transcoding some live audio stream from M1-L2/AAC to PCM .wav, on the fly.

Then, I need to feed NAudio with that live .wav audio stream, because it doesn't come from any audio device (mic/speaker) but from the network.

I'm wondering if it is possible to use a named pipe (fifo) as input ?

I'm aware this may have its own challenge since ultimately this named pipe is a kinda file, so NAudio may not understand what's going on, therefore my question.

If the above is not possible, is there any other alternatives?

System.Threading.ThreadAbortException

Exception thrown: 'System.Threading.ThreadAbortException' in mscorlib.dll

I use VS2017, it's not working after successfully built. Showing error on when I pressing "o" or "m".

image

WaveFileReaderExtensions.cs suggestion

To support detect DTMF from all NAudio supported file types, change "WaveFileReaderExtensions.cs" like this:

namespace DtmfDetection.NAudio
{
    using System.Collections.Generic;

    using global::NAudio.Wave;

    public static class WaveFileReaderExtensions
    {
        public static IEnumerable<DtmfOccurence> DtmfTones(this WaveStream waveFile)
        {
            var dtmfAudio = new DtmfAudio(new StaticSampleSource(waveFile));

            while (dtmfAudio.WaitForDtmfTone() != DtmfTone.None)
            {
                var start = waveFile.CurrentTime;
                dtmfAudio.WaitForEndOfLastDtmfTone();
                var duration = waveFile.CurrentTime - start;

                yield return new DtmfOccurence(dtmfAudio.LastDtmfTone, start, duration);
            }
        }


    }
}

Thanks.

How to implement microphone volume

Hello everyone, I have been using the class to identify flames but I would like to increment my software with volumes and vu meter. Anyone have any practical examples in C # or VB.NET so I can test?

thank you.

Repeating DTMF tones are not detected correctly when played too fast

When a DTMF tone is repeatedly played in short intervals, the DTMF detector will sometimes fail to tell them apart and report them as a single DTMF tone occurence instead.

Repro Steps

Repro Steps (Alternative)

Note

This could be an issue with the test data. The tones might be played faster than the typical Mark/Space of 40/40, which the detector assumes as minimum.

Can the project be built on Linux?

Hello @bert2

First of all, thanks for this great project.

I'm trying to compile it for Linux, and even though I installed dotnet, powershell, and all, I keep getting the following error:

./build.ps1
Preparing to run build script...
Running build script...
**Error: Failed to install addin 'Cake.Git'.**

And with verbose, I get that :

Installing addins...
  CACHE https://api.nuget.org/v3/registration5-gz-semver2/cake.codecov/index.json
The addin Cake.Codecov will reference Cake.Codecov.dll.
  CACHE https://api.nuget.org/v3/registration5-gz-semver2/cake.git/index.json
Assemblies not found for tfm .NETFramework,Version=v4.6.1 and rid [NULL].
No assemblies found after running content resolver.
Error: Cake.Core.CakeException: Failed to install addin 'Cake.Git'.
  at Cake.Core.Scripting.ScriptProcessor.InstallAddins (System.Collections.Generic.IReadOnlyCollection`1[T] addins, Cake.Core.IO.DirectoryPath installPath) [0x000c0] in <040622b6758f456b89fd2a5b24e3e64c>:0
  at Cake.Core.Scripting.ScriptRunner.Run (Cake.Core.Scripting.IScriptHost host, Cake.Core.IO.FilePath scriptPath) [0x0013b] in <040622b6758f456b89fd2a5b24e3e64c>:0
  at Cake.Features.Building.BuildFeature.RunCore (Spectre.Console.Cli.IRemainingArguments arguments, Cake.Features.Building.BuildFeatureSettings settings) [0x0010d] in <21a7d41a88904b88a0986b85ed354c53>:0
  at Cake.Features.Building.BuildFeature.Run (Spectre.Console.Cli.IRemainingArguments arguments, Cake.Features.Building.BuildFeatureSettings settings) [0x00012] in <21a7d41a88904b88a0986b85ed354c53>:0
  at Cake.Commands.DefaultCommand.Execute (Spectre.Console.Cli.CommandContext context, Cake.Commands.DefaultCommandSettings settings) [0x000d0] in <21a7d41a88904b88a0986b85ed354c53>:0

My dotnet info :

dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.300
 Commit:    8473146e7d

Runtime Environment:
 OS Name:     debian
 OS Version:  11
 OS Platform: Linux
 RID:         debian.11-arm64
 Base Path:   /home/xxxxxxxx/.dotnet/sdk/6.0.300/

Host (useful for support):
  Version: 6.0.5
  Commit:  70ae3df4a6

.NET SDKs installed:
  6.0.300 [/home/xxxxx/.dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.5 [/home/xxxxx/.dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.5 [/home/xxxxx/.dotnet/shared/Microsoft.NETCore.App]

Do you know what is causing the issue? I tried to pin Cake addin to other versions but the issue remains.

Thanks for your help in advance.

FSK CallerID decoder

Hi, any plans to implement an FSK CallerID decoder? Thanks in advance. Best, Angelo

Identify off-hook phone

Hello, I use the dll some time ago. I implemented a timer in my code to identify the conversation. When this timer reaches 15 seconds of silence, I close the recording of the conversation.
But often the voice of who is on the other side of the line is very low, which ends up in the middle of the conversation breaking the recording.
In short, is there a way to identify if the phone is off the hook, via audio, or some other dll?
Thank you.

LiveAudioDtmfAnalyzer seems not working

I'm unable to run both the samples
How to detect DTMF tones through mic in, but with each audio channel analyzed separately and
How to detect DTMF tones in the current audio output
and i suspect there is something not working in LiveAudioDtmfAnalyzer

I am testing playing DTMF sound from cell phone app and the site http://onlinetonegenerator.com/dtmf.html

I am placing cell phone in front of my external microphone, as well of the speaker of my PC if I use the generator online.

Please consider that:

  • If I just use N-Audio, I am able to obtain correctly data from WaveInEvent micIn, and execute, for example, the FFT - so I have no doubt that my microphone is working correctly with N-Audio acquisition.

  • When testing scenario How to detect DTMF tones in the current audio output I have activated the listening of the microphone input in Windows audio properties.

  • If i use the example named How to print all DTMF tones in an MP3 file, but with each audio channel analyzed separately: it works well.

I hope this can help you to improve your library!

Thanks and regards,
Francesco

Expected Behavior

I'm expecting DtmfToneStarted and DtmfToneStopped events are raised.

Current Behavior

DtmfToneStarted and DtmfToneStopped events are never raised.

DTMF detector fails for 48KHz audio

DTMF detector is not working correctly with audio files that have a sample a rate of 48 KHz.

Expected Behavior

  • Detector should work with 48 KHz audio.

Current Behavior

  • When 48 KHz audio is fed to the detector it will miss actual DTMF tones and find false positives.

Possible Cause

  • NAudio's WdlResamplingSampleProvider seems to be unable to suppress aliasing effects when downsampling from 48 KHz to 8 KHz

Steps to Reproduce

  1. Run integration test AudioFileTests.ShouldDownsample48KHzCorrectly()

Possible Solution

  • Try another of NAudio's resamplers
  • Low-pass filter the audio data with cut-off at 8 KHz before downsampling

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.