Tuesday 30 July 2019

C# - Windows WASAPI - Record loopback

I have been playing with multimedia programs such as ffmpeg which has wonderful video processing capabilities in converting/re-encoding one file format to another but I was less impressed by its sound processing as it was always outclassed by Audacity. Audacity is king but I wonder how best to replicate its functionality from the command line or from code. In this post, I show the C# way to use the WASAPI interface via CSCore to record the sound loopback.

Give yourself a console program, and then NuGet CSCore by Florian (thanks) and then copy in the following code


using CSCore.Codecs.WAV;
using CSCore.SoundIn;

using System;

namespace CSCoreWasapiLoopbackExperiment
{
    class Program
    {
        static void Main(string[] args)
        {
            using (WasapiCapture capture = new WasapiLoopbackCapture())
            {
                //if nessesary, you can choose a device here
                //to do so, simply set the device property of the capture to any MMDevice
                //to choose a device, take a look at the sample here: http://cscore.codeplex.com/

                Console.WriteLine("Waiting, press any key to start");
                Console.ReadKey();
                //initialize the selected device for recording
                capture.Initialize();

                //create a wavewriter to write the data to
                using (WaveWriter w = new WaveWriter("dump.wav", capture.WaveFormat))
                {
                    //setup an eventhandler to receive the recorded data
                    capture.DataAvailable += (s, e) =>
                    {
                        //save the recorded audio
                        w.Write(e.Data, e.Offset, e.ByteCount);
                    };

                    //start recording
                    capture.Start();

                    Console.WriteLine("Started, press any key to stop");
                    Console.ReadKey();

                    //stop recording
                    capture.Stop();
                }
            }
        }
    }
}

So now you can record your computer's speaker output.

Tuesday 9 April 2019

WPF - XAML - How to SkewTransform a Video

It is possible to SkewTransform a video to make it look like a special effect from a Hollywood film. I made a video to demonstrate. I also made a quick explanatory video. As promised, here is the source code.

<Window x:Class="BrexitThisIsFine.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BrexitThisIsFine"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Viewbox Stretch="Uniform">
        <StackPanel Grid.Column="0">

            <Canvas ClipToBounds="True"   Width="480" Height="330">
                <MediaElement Canvas.Left="150" Canvas.Top="100" Width="240" Height="150" x:Name="foo" Source="C:\Documents\My Movie.mp4" >
                    <MediaElement.RenderTransform>
                        <SkewTransform x:Name="skew" CenterX="40" CenterY="50"/>
                    </MediaElement.RenderTransform>
                </MediaElement>

                <!-- Animate the video: -->
                <Canvas.Triggers>
                    <EventTrigger RoutedEvent="Canvas.Loaded">
                        <BeginStoryboard>
                            <Storyboard RepeatBehavior="Forever" >
                                <DoubleAnimation Storyboard.TargetName="skew" Storyboard.TargetProperty="AngleX" From="0" To="360" Duration="0:0:50"/>
                                <DoubleAnimation Storyboard.TargetName="skew" Storyboard.TargetProperty="AngleY" From="0" To="360" Duration="0:0:50"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Canvas.Triggers>
            </Canvas>
        </StackPanel>
    </Viewbox>
</Window>

Saturday 16 March 2019

C# - Picking nice graph axes functionally

So I am revising XAML and I wanted to write a graph control. The first problem I encountered is how to pick nice numbers for your graph axis, the algorithm is not obvious and I had to google before I found a popular answer on Stack Overflow.

The original given code was quite classy when it needn't be given that we are not holding state. I think a functional implementation is more appropriate and so I have rewritten the C# code a class with static members only. I have abolished the class member variables and instead I am returning all the facts (short for artefacts) found in the function via an object array. I have been influenced by Python of late with its capability to return tuples but I have avoided the Tuple<> generic class because I didn't want to have to specify all the types ( I appreciate there is a small performance hot with the boxing).

using System;

namespace GraphIntervalTest
{
    class Program
    {
        static void Main(string[] args)
        {

            object[] facts = NiceScaleStatic.CalculateNiceScale(1.2813, 1.331, 8);  

            System.Console.Out.WriteLine("Tick Spacing:\t" + facts[0]);
            System.Console.Out.WriteLine("Nice Minimum:\t" + facts[1]);
            System.Console.Out.WriteLine("Nice Maximum:\t" + facts[2]);
            System.Console.Out.WriteLine("Tick Count:\t" + facts[3]);
            System.Console.Out.WriteLine("Min:\t" + facts[4]);
            System.Console.Out.WriteLine("Max:\t" + facts[5]);
            System.Console.Out.WriteLine("MaxTicks:\t" + facts[6]);
        }
    }

    public class NiceScaleStatic
    {
        public static object[] CalculateNiceScale(double min, double max, long maxTicks = 10)
        {
            double tickSpacing;
            double range;
            double niceMin;
            double niceMax;
            long tickCount;

            range = niceNum(max - min, false);
            tickSpacing = niceNum(range / (maxTicks - 1), true);
            niceMin = Math.Floor(min / tickSpacing) * tickSpacing;
            niceMax = Math.Ceiling(max / tickSpacing) * tickSpacing;

            tickCount = (long)((niceMax - niceMin) / tickSpacing);

            return new object[] { tickSpacing, niceMin, niceMax, tickCount, min, max, maxTicks };
        }

        static double niceNum(double range, bool round)
        {
            double exponent; /** exponent of range */
            double fraction; /** fractional part of range */
            double niceFraction;  /** nice, rounded fraction */

            exponent = Math.Floor(Math.Log10(range));
            fraction = range / Math.Pow(10, exponent);

            if (round)
            {
                if (fraction < 1.5)
                    niceFraction = 1;
                else if (fraction < 3)
                    niceFraction = 2;
                else if (fraction < 7)
                    niceFraction = 5;
                else
                    niceFraction = 10;
            }
            else
            {
                if (fraction <= 1)
                    niceFraction = 1;
                else if (fraction <= 2)
                    niceFraction = 2;
                else if (fraction <= 5)
                    niceFraction = 5;
                else
                    niceFraction = 10;
            }

            return niceFraction * Math.Pow(10, exponent);
        }
    }
}