Monday, July 23, 2018

Solution of the differential equation

'Cephes Math Library Release 2.8:  June, 2000
'Copyright by Stephen L. Moshier
'    * Sergey Bochkanov (ALGLIB project). Translation from C to
'      pseudocode.
'See subroutines comments for additional copyrights.
'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, or (at your option) any later version.
'This program is distributed in the hope that it will be useful,
'but WITHOUT ANY WARRANTY; without even the implied warranty of
'GNU General Public License for more details.
'A copy of the GNU General Public License is available at
'Airy function
'Solution of the differential equation
'y"(x) = xy.
'The function returns the two independent solutions Ai, Bi
'and their first derivatives Ai'(x), Bi'(x).
'Evaluation is by power series summation for small x,
'by rational minimax approximations for large x.
'Error criterion is absolute when function <= 1, relative
'when function > 1, except * denotes relative error criterion.
'For large negative x, the absolute error increases as x^1.5.
'For large positive x, the relative error increases as x^1.5.
'Arithmetic  domain   function  # trials      peak         rms
'IEEE        -10, 0     Ai        10000       1.6e-15     2.7e-16
'IEEE          0, 10    Ai        10000       2.3e-14*    1.8e-15*
'IEEE        -10, 0     Ai'       10000       4.6e-15     7.6e-16
'IEEE          0, 10    Ai'       10000       1.8e-14*    1.5e-15*
'IEEE        -10, 10    Bi        30000       4.2e-15     5.3e-16
'IEEE        -10, 10    Bi'       30000       4.9e-15     7.3e-16
'Cephes Math Library Release 2.8:  June, 2000
'Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier
Public Sub Airy(ByVal x As Double, _
         ByRef Ai As Double, _
         ByRef Aip As Double, _
         ByRef Bi As Double, _
         ByRef Bip As Double)
    Dim z As Double
    Dim zz As Double
    Dim t As Double
    Dim f As Double
    Dim g As Double
    Dim uf As Double
    Dim ug As Double
    Dim k As Double
    Dim zeta As Double
    Dim theta As Double
    Dim domflg As Long
    Dim c1 As Double
    Dim c2 As Double
    Dim sqrt3 As Double
    Dim sqpii As Double
    Dim AFN As Double
    Dim AFD As Double
    Dim AGN As Double
    Dim AGD As Double
    Dim APFN As Double
    Dim APFD As Double
    Dim APGN As Double
    Dim APGD As Double
    Dim AN As Double
    Dim AD As Double
    Dim APN As Double
    Dim APD As Double
    Dim BN16 As Double
    Dim BD16 As Double
    Dim BPPN As Double
    Dim BPPD As Double

    sqpii = 0.564189583547756
    c1 = 0.355028053887817
    c2 = 0.258819403792807
    sqrt3 = 1.73205080756888
    domflg = 0#
    If x > 25.77 Then
        Ai = 0#
        Aip = 0#
        Bi = MaxRealNumber
        Bip = MaxRealNumber
        Exit Sub
    End If
    If x < -2.09 Then
        domflg = 15#
        t = Sqr(-x)
        zeta = -(2# * x * t / 3#)
        t = Sqr(t)
        k = sqpii / t
        z = 1# / zeta
        zz = z * z
        AFN = -0.131696323418332
        AFN = AFN * zz - 0.626456544431912
        AFN = AFN * zz - 0.693158036036933
        AFN = AFN * zz - 0.279779981545119
        AFN = AFN * zz - 0.04919001326095
        AFN = AFN * zz - 4.06265923594885E-03
        AFN = AFN * zz - 1.59276496239262E-04
        AFN = AFN * zz - 2.77649108155233E-06
        AFN = AFN * zz - 1.67787698489115E-08
        AFD = 1#
        AFD = AFD * zz + 13.3560420706553
        AFD = AFD * zz + 32.6825032795225
        AFD = AFD * zz + 26.73670409415
        AFD = AFD * zz + 9.1870740290726
        AFD = AFD * zz + 1.47529146771666
        AFD = AFD * zz + 0.115687173795188
        AFD = AFD * zz + 4.40291641615211E-03
        AFD = AFD * zz + 7.54720348287414E-05
        AFD = AFD * zz + 4.5185009297058E-07
        uf = 1# + zz * AFN / AFD
        AGN = 1.97339932091686E-02
        AGN = AGN * zz + 0.391103029615688
        AGN = AGN * zz + 1.06579897599596
        AGN = AGN * zz + 0.93916922981665
        AGN = AGN * zz + 0.351465656105548
        AGN = AGN * zz + 6.33888919628925E-02
        AGN = AGN * zz + 5.85804113048388E-03
        AGN = AGN * zz + 2.82851600836737E-04
        AGN = AGN * zz + 6.98793669997261E-06
        AGN = AGN * zz + 8.11789239554389E-08
        AGN = AGN * zz + 3.41551784765924E-10
        AGD = 1#
        AGD = AGD * zz + 9.30892908077442
        AGD = AGD * zz + 19.8352928718312
        AGD = AGD * zz + 15.5646628932865
        AGD = AGD * zz + 5.47686069422975
        AGD = AGD * zz + 0.954293611618962
        AGD = AGD * zz + 8.64580826352392E-02
        AGD = AGD * zz + 4.12656523824223E-03
        AGD = AGD * zz + 1.01259085116509E-04
        AGD = AGD * zz + 1.17166733214414E-06
        AGD = AGD * zz + 4.9183457006293E-09
        ug = z * AGN / AGD
        theta = zeta + 0.25 * PI()
        f = Sin(theta)
        g = Cos(theta)
        Ai = k * (f * uf - g * ug)
        Bi = k * (g * uf + f * ug)
        APFN = 0.185365624022536
        APFN = APFN * zz + 0.886712188052584
        APFN = APFN * zz + 0.987391981747399
        APFN = APFN * zz + 0.401241082318004
        APFN = APFN * zz + 7.10304926289631E-02
        APFN = APFN * zz + 5.90618657995662E-03
        APFN = APFN * zz + 2.33051409401777E-04
        APFN = APFN * zz + 4.08718778289035E-06
        APFN = APFN * zz + 2.48379932900442E-08
        APFD = 1#
        APFD = APFD * zz + 14.7345854687503
        APFD = APFD * zz + 37.542393343549
        APFD = APFD * zz + 31.4657751203046
        APFD = APFD * zz + 10.9969125207299
        APFD = APFD * zz + 1.78885054766999
        APFD = APFD * zz + 0.141733275753663
        APFD = APFD * zz + 5.44066067017226E-03
        APFD = APFD * zz + 9.39421290654511E-05
        APFD = APFD * zz + 5.65978713036027E-07
        uf = 1# + zz * APFN / APFD
        APGN = -3.55615429033082E-02
        APGN = APGN * zz - 0.637311518129436
        APGN = APGN * zz - 1.70856738884312
        APGN = APGN * zz - 1.50221872117317
        APGN = APGN * zz - 0.563606665822103
        APGN = APGN * zz - 0.102101031120217
        APGN = APGN * zz - 9.48396695961445E-03
        APGN = APGN * zz - 4.60325307486781E-04
        APGN = APGN * zz - 1.14300836484517E-05
        APGN = APGN * zz - 1.33415518685547E-07
        APGN = APGN * zz - 5.63803833958894E-10
        APGD = 1#
        APGD = APGD * zz + 9.8586580169613
        APGD = APGD * zz + 21.6401867356586
        APGD = APGD * zz + 17.3130776389749
        APGD = APGD * zz + 6.17872175280829
        APGD = APGD * zz + 1.08848694396321
        APGD = APGD * zz + 9.95005543440888E-02
        APGD = APGD * zz + 4.78468199683887E-03
        APGD = APGD * zz + 1.18159633322839E-04
        APGD = APGD * zz + 1.37480673554219E-06
        APGD = APGD * zz + 5.79912514929148E-09
        ug = z * APGN / APGD
        k = sqpii * t
        Aip = -(k * (g * uf + f * ug))
        Bip = k * (f * uf - g * ug)
        Exit Sub
    End If
    If x >= 2.09 Then
        domflg = 5#
        t = Sqr(x)
        zeta = 2# * x * t / 3#
        g = Exp(zeta)
        t = Sqr(t)
        k = 2# * t * g
        z = 1# / zeta
        AN = 0.346538101525629
        AN = AN * z + 12.0075952739646
        AN = AN * z + 76.2796053615235
        AN = AN * z + 168.089224934631
        AN = AN * z + 159.756391350164
        AN = AN * z + 70.5360906840444
        AN = AN * z + 14.026469116339
        AN = AN * z + 1#
        AD = 0.56759453263877
        AD = AD * z + 14.7562562584847
        AD = AD * z + 84.5138970141475
        AD = AD * z + 177.3180881454
        AD = AD * z + 164.23469287153
        AD = AD * z + 71.4778400825576
        AD = AD * z + 14.0959135607834
        AD = AD * z + 1#
        f = AN / AD
        Ai = sqpii * f / k
        k = -(0.5 * sqpii * t / g)
        APN = 0.613759184814036
        APN = APN * z + 14.7454670787755
        APN = APN * z + 82.0584123476061
        APN = APN * z + 171.184781360976
        APN = APN * z + 159.317847137142
        APN = APN * z + 69.9778599330103
        APN = APN * z + 13.9470856980482
        APN = APN * z + 1#
        APD = 0.334203677749737
        APD = APD * z + 11.1810297306158
        APD = APD * z + 71.172735214786
        APD = APD * z + 158.778084372838
        APD = APD * z + 153.206427475809
        APD = APD * z + 68.675230459278
        APD = APD * z + 13.8498634758259
        APD = APD * z + 1#
        f = APN / APD
        Aip = f * k
        If x > 8.3203353 Then
            BN16 = -0.253240795869364
            BN16 = BN16 * z + 0.575285167332467
            BN16 = BN16 * z - 0.329907036873225
            BN16 = BN16 * z + 0.06444040689482
            BN16 = BN16 * z - 3.82519546641337E-03
            BD16 = 1#
            BD16 = BD16 * z - 7.15685095054035
            BD16 = BD16 * z + 10.6039580715665
            BD16 = BD16 * z - 5.23246636471251
            BD16 = BD16 * z + 0.957395864378384
            BD16 = BD16 * z - 0.055082814716355
            f = z * BN16 / BD16
            k = sqpii * g
            Bi = k * (1# + f) / t
            BPPN = 0.465461162774652
            BPPN = BPPN * z - 1.08992173800494
            BPPN = BPPN * z + 0.638800117371828
            BPPN = BPPN * z - 0.126844349553103
            BPPN = BPPN * z + 7.6248784434211E-03
            BPPD = 1#
            BPPD = BPPD * z - 8.70622787633159
            BPPD = BPPD * z + 13.8993162704553
            BPPD = BPPD * z - 7.14116144616431
            BPPD = BPPD * z + 1.34008595960681
            BPPD = BPPD * z - 7.84273211323342E-02
            f = z * BPPN / BPPD
            Bip = k * t * (1# + f)
            Exit Sub
        End If
    End If
    f = 1#
    g = x
    t = 1#
    uf = 1#
    ug = x
    k = 1#
    z = x * x * x
    Do While t > MachineEpsilon
        uf = uf * z
        k = k + 1#
        uf = uf / k
        ug = ug * z
        k = k + 1#
        ug = ug / k
        uf = uf / k
        f = f + uf
        k = k + 1#
        ug = ug / k
        g = g + ug
        t = Abs(uf / f)
    uf = c1 * f
    ug = c2 * g
    If domflg Mod 2# = 0# Then
        Ai = uf - ug
    End If
    If domflg \ 2# Mod 2# = 0# Then
        Bi = sqrt3 * (uf + ug)
    End If
    k = 4#
    uf = x * x / 2#
    ug = z / 3#
    f = uf
    g = 1# + ug
    uf = uf / 3#
    t = 1#
    Do While t > MachineEpsilon
        uf = uf * z
        ug = ug / k
        k = k + 1#
        ug = ug * z
        uf = uf / k
        f = f + uf
        k = k + 1#
        ug = ug / k
        uf = uf / k
        g = g + ug
        k = k + 1#
        t = Abs(ug / g)
    uf = c1 * f
    ug = c2 * g
    If domflg \ 4# Mod 2# = 0# Then
        Aip = uf - ug
    End If
    If domflg \ 8# Mod 2# = 0# Then
        Bip = sqrt3 * (uf + ug)
    End If
End Sub

Monday, June 11, 2018

Graphical audio spectrum visualizer: Trick Spectrum from Microphone, MP3 players, Win10, in Visual Basic 6.0

Here we have a superb app made by Krivous Anatoly Anatolevich. Here is his description on VBForums: The sound is analyzed through a standard recording device, i.e. you can select the microphone and view its spectrum, or you can select stereo mixer and view a spectrum of a playback sound.
Captures via my Microphone

This visualizer allows to adjust the number of displayed octaves, transparency of background and amplification.
You can also load a palette from an external PNG file with 32ARGB format. It also supports the following effects: "blur" and "burning". You can view a spectrum of a signal represented in the two modes: arcs (rings) and sectors (pies). If you use the ring view an octave is mapped to radial coordinate and a semitone to angle. The separated harmonics are placed along the same line; color represents an intensity. The sectors view maps the amount of signal to the radial coordinate, the frequency in octaves to the color, the frequency in semitones to the angular coordinate.
This idea was suggested to me by Vladislav Petrovky (aka Hacker). His idea was a little different.

Initially it creates the buffers for sound and buffer bitmaps. Further it starts the sound capture process and waits when a buffer will be filled. When a buffer has been filled it begins processing. Firstly it performs the Fast Fourier Transform in order to transform a signal to the frequency domain form. Before performing it applies the Hamming window in order to reduce distortions because a signal has discontinuity at the edges of a buffer. When a signal has been translated to the frequency domain the buffer contains complex value that represent the vectors. The module (length) of a vector implies the energy of signal in that frequency and the argument (angle) implies phase of a harmonic in that frequency.

Screenshot of the menu
We need the energy of frequency although the phase information allows to determine the frequency more accurately considering the phase difference. I don't use phase information in this project. The drawing method is different for each appearance mode. In order to boost the work it uses the precalculated coordinates named MapData. This array contains the angles of arcs and sectors for the current appearance mode. When coordinates has been calculated it calculates the amount of frequency for each FFT bin figuring out the length of a vector. This value is uses as the index in the color palette after converting the value to a range from 0 to 255. Further GDI+ draws the necessary primitives depending on the appearance mode. Note that all drawing occur onto the buffer bitmap not on window. I specially have not mentioned about the Release procedure that animates the background. This procedure applies an effect to the buffer bitmap before signal processing. It uses the Fade property that determines the speed of the disappearance of previous drawing bitmap. It just decrease the alpha value of the entire bitmap. When you use an effect it also works with the bits of the buffer bitmap and decreases the alpha value. For instance, if the blur effect has been selected it averages the near pixels (analog of low-pass filtering) then it decreases the alpha value for all pixels depending on Fade property. Eventually it draws buffer bitmap onto the main window. Thus it draws the energy of the spectrum of signal in the polar coordinates. It can be used as the start point for the notes or chord recognition.

Download from VBForums

Download from me

Here is a video of the app:

Friday, May 18, 2018

Voice coder

Description: Creating music, I've seen a lot of different virtual instruments and effects. One of the most interesting effects is the vocoder, which allows you to modulate his voice and make it look like a voice for example a robot or something like that. Vocoder was originally used to compress the voice data, and then it began to be used in the music industry. Because I had free time, I decided to write something like this for the sake of the experiment and describe in detail the stages of development for VB6.

Download from VBForums
Download from Me

So, take a look at the simplest scheme vocoder:

The signal from the microphone (speech) is fed to a bank of band-pass filters, each of which passes only a small part of the frequency band of the speech signal. The greater the number of filters - the better speech intelligibility. At the same time, the carrier signal (e.g. ramp) is also passed through the same filter bank. Filter output speech signal is fed to envelope detectors which control modulators and outputs a filter carrier signal passes to the other input of the modulator. As a result, each band speech signal adjusts the level of the corresponding band carrier (modulates it). Further, output signals from all modulators are mixed and sent to the output. Further, all signal modulators are mixed and sent to the output. In order to improve speech intelligibility also apply additional blocks, such as the detector "sizzling" sound. So, to begin development necessary to determine the source signals, where they will take. It is possible for example to capture data from a file or directly processed in real-time from a microphone or line input. To test very easy to use file, so we will do and so and so. As the carrier will use an external file looped in a circle, to adjust the tone simply add the ability to change the playback speed, which will change the tone. To capture the sound of the file will use Audio Compression Manager (ACM), with it very convenient to make conversion between formats (because the file can be in any format, you would have to write some functions to different formats). It may be that to convert to the desired format will not correct ACM drivers, then play this file will not be available (although you can try to do it in 2 stages). As input files will use the wav - files, because to work with them in the system has special features to facilitate retrieving data from them.

When loading of form we perform initialization of all components. Capture, playing back the audio size FFT, the amount of overlap, overlapping buffers, creating buffers for integer and complex data. Next, I made a box shape with rounded corners, as use a window without frame (draw in the nonclient area had no desire). Now the whole problem is reduced to handling events - AudioPlayback_NewData and AudioCapture_NewData. First event occurs when the playback device needs another portion of the audio data, the second when the buffer capture, in which we simply copy the data into a temporary buffer from where it will take them at processing AudioPlayback_NewData. The main method - Process, in it we just do the conversion. First we check whether we capture from a file or device. To do this, we check the variable mInpFile, which specifies the name of the input file to capture. If capture is made from a file, then we are using object inpConv, which is an instance of clsTrickWavConverter, convert the data into the format you want us to. If the data is finished (the number of bytes read does not match the passed), it means that we are on the edge of the file and continue to have to start over again. Also check the carrier signal and if it is not set then just copy the input data on output and, in this case, we will hear the raw sound. Otherwise, we translate the data into a complex form (count a real part of the signal and the imaginary zero out) and puts the resulting array in an overlapping buffer. Next, start processing the carrier signal. Because carrier signal we can have a very small length (you can use one wave period), in order to optimize I will do the repetition of the signal if required. Let me explain. For example, if we have a carrier signal 10 ms and 100 ms buffer (for example), then you could just call the conversion each time using ACM overwriting the pointer to the array destination, but it is not optimal. For optimization can be converted only once, and then simply duplicate the data to the end of the array, which we did. Only then do not forget to change the position in the source file, otherwise the next phase of the reading will not be the same and will flicks. We will write to another buffer (rawBuffer). This buffer length is based on the pitch shift. For example, if we want to shift the tone for the amount of semitones (halftones), the buffer size must be rawBuffer 2semitones / 12 times more. Then we simply compress / stretch buffer to a value mFFTSize, which will give us the acceleration / deceleration, and as a result increase / decrease tone. After all the manipulations we write data in an overlapping buffer and start processing. To do this, we pass by the number of overlapping data and handle them. Class objects clsTrickOverlappedBuffer return us the correct data. Processing is clear from the code, as We consider in detail the performance of each class. After processing all of overlap we get the output and convert them to integer suitable for playback. As the setting uses a form frmSettings. As the list of devices using a standard listbox, just going through my drawing class. The list of devices will be added in the following order:
  • A default device predetermined format
  • Device 1
  • Device 2
  • ...
  • Device n
  • Capturing from a file

For testing click on the last point message is used LB_GETITEMRECT, which receives the coordinates and size of the item in the list. If this is not done then click the sheet of paper, if there is an empty space at the bottom will be equivalent to clicking on the last point. In the handler settings button in the main form frmTrickVocoder we check capture device and either open the file for conversion or initialize capture. To adjust the volume and mixing using a logarithmic scale, as the sensitivity of the human ear is not linear.


Thursday, May 17, 2018

The story of how Microsoft ran itself into the ground: by Lofaday

The first lesson I learned about "dumb decisions" from Microsoft was when they trashed hardware access when upgrading from Windows 98 and ME. No one noticed that the decision instantly put many thousands of small businesses in trouble, mine included, because the hardware layer access in Windows 98 was the cornerstone of many industrial control systems. All of a sudden I found myself in dozens of meetings explaining to clients why non-NT was no longer made!
An entire Industrial rack mount computer industry was wiped out overnight. The millions invested in motherboard I/O (including printed circuit boards we developed), now wasted. The information systems I sold each meant many MS OS sales. MS didn't even seem to notice or care that their industrial usage arm was holed below the waterline. (Now, in industrial control solutions, Linux has taken over .. it could have been MS).
XP is a lot more stable, and indeed most NT based upgrades have been a considerable improvement, but it wouldn't have been rocket science for Microsoft to have added backward compatibility.
In more general terms, Microsoft need to learn "consistency consistency consistency". The ignorance and arrogance of Microsoft is bewildering if not epic. Ever since Gates left, they seem incapable of applying common sense. Someone in Harvard must have done a popular thesis on obsolescence because MS has adopted it as their mantra! They have abandoned VB6, XNA, SilverLight, Skype API, the original MS Office menus, VS Java, Foxpro, Access, .... Each time they do this, people jump out windows with their careers and businesses in tatters. But Microsoft are just psychopathic at times in their ability to ignore the plight of their own customers. At one point in W8, they even tried to get rid of the desktop! It beggars belief that they expected clients to sit at desks with administrative software running on something that looked more like a giant mobile phone than a desktop PC. Success breeds contempt!
A great story: Once there were two microprocessor giants, Intel and Motorola. Motorola had the best and fastest 16-bit chips and were set to dominate. Then they both decided to come out with a 32 bit chip. Motorola's was miles better than the Intel 32-bit device. Miles better! BUT Intel did something Motorola decided wasn't important... Intel maintained backward compatibility with their original 16-bit processes. Motorola BETRAYED their own client base. They just stuck up their middle finger and said "go back to school and re-learn". NOW -- how many people have heard of Motorola processors these days? And that's the same dumb mistake Microsoft make over and over again. "CONSISTENCY CONSISTENCY CONSISTENCY".
The lesson is that when people buy tech, they are making a decision to invest colossal amounts of time in experience in that tech. My customers used to demand Microsoft because they thought the bigger the company, the safer their investment. But now they are just confused. Even banks use opensource.
Once a company has shown a willingness to betray customer investment with gaay abandon, that company is dead. Teach that one in Harvard!
An article by 

Friday, April 20, 2018

Visual Basic 6 SP6 Working in Windows 10 64-bit

Visual Basic 6 SP6 Working in Windows 10 64-bit

Again, thank you to the advanced programmers of the VB6 community!

Sunday, April 1, 2018

Stepper Motor Appliance with Visual Basic 6.0

In a previous article entitled "Range finder with a Laser and a Webcam in Visual Basic 6.0" we have shown a project for distance estimation by using a laser and a webcam. Here we have another interesting project by Dr. Todd Danko, that uses VB6.

Download from ME


The most advanced pieces of scientific equipment are often both very rare and very expensive. This is a poor combination if your research needs dictate the use of such a device.
For example, scanning electron microscopes, depending on quality, cost millions of dollars. This high price prohibits their purchase for occasional research use. This high price also decreases the probability that there is a facility near by that has both, a scanning electron microscope, and has time to let you use it.
One solution to this scenario is to create a product that allows scanning electron microscopes and possibly other equipment to be used remotely. Such a device would utilize the internet to pass commands to the microscope, as well as return data from the microscope back to the user.

Proposed Solution

Build a prototype of an internet controlled appliance that simulates the remote operation of a scanning electron microscope.
This appliance has four main components:
  • Server Software - to interface with the mechanical device, and the internet
  • Client Software - to interface with the user, and the internet
  • Camera -to simulate output data similar to that of a microscope
  • Stepper Motor X-Y table - to allow the user to change the position of the subject under the microscope



Internet Appliance Electro-Mechanical Parts
PartVendorPart #QuantityPrice
Stepper MotorJameco1518612$6.19
Diode (1N4003)Jameco769708$0.04
Gears (Assorted)Jameco1318011$7.99
Power DriverArrowUNC5804B2$4.22
8255 CardBoondog.com8255 Kit1$59.00
Logitech WebCamBest Buy961237-04031$49.99
BreadboardRadio Shack276-1741$13.49
Battery HolderRadio Shack270-3961$1.79
Wrapping WireRadio Shack278-5011$2.99


This internet appliance calls for several electro-mechanical components:
  • An X-Y table that is moved by two stepper motors.
    • The X-Y table's purpose is to move the subject of the camera up/down, left/right in the camera's field of view.
    • The X-Y table is constructed of a wood frame, with stepper motors gear mounted.
    • Rotational motions of the stepper motors are transferred into linear motion by a system of wires and pulleys.
  • An 8255 card to interface the server PC with the stepper motor control circuits.
    • The 8255 cards purpose is to provide an interface between the server PC and the stepper motor control circuits. More information on the 8255 card's construction and operation may be found at
  • Two stepper motor control circuits.
    • The 5804 chips take digital data in from the 8255 card, and convert it into the phasing of the stepper motor coils to rotate the stepper motors in the desired directions by specified amounts.
    • Below is a schematic for one stepper motor control circuit. Please note that this appliance requires two motors, so two of these circuits must be constructed. The first control circuit is connected to ports A.0 and A.1 of the 8255 card. The second control circuit is connected to ports B.0 and B.1 of the 8255 card.

  • A Camera to collect image data.
    • A camera captures pictures of the subject on the X-Y table.
    • The camera is connected to the server PC in this case through a USB cable.
    • The drivers included with the camera were used to capture image data through the USB port of the server PC.
  • Below is a picture of the overall electro-mechanical setup.
    • Note the ribbon cable coming in from the top of the scene. This attaches to the 8255 card.
    • Note the stepper motor control circuits to the left of the photo.
    • A battery pack is used to power the stepper motors.
    • Note the use of wires wrapped around posts on the X-Y table to convert the motor's motion into linear movement. The platform that the subject would be placed on has been removed to expose the X-Y table mechanism.


The software to control this internet appliance is broken into two groups: Software on the server PC and software on the client PC.


  • The purpose of the server is to interface the internet appliance with the internet.

  • The server has several software components:
    • Visual Basic software to communicate with the client through WinSock, and to communicate with the 8255 Card through the 8255.dll.
    • The visual basic software contains the following components and may be downloaded at the bottom of this page as
      • stepper_control_server.frm
      • stepper_control_server.vbp
      • stepper_control_server.vbw
      • stepper_control_server.exe
    • FTP Server software to make image files written by the Visual Basic software available for retrieval by the client software. For this project, the Titan FTP Server was used.
    • ActiveX component to accept data from the camera, and save it to a file on the server PC's hard drive. For this project, WebCam OCX was used.
  • Note, ActiveX components as well as the 8255.dll must be installed on a PC in addition to the Visual Basic Executable in order for the server to properly function.


  • The purpose of the client is to interface the user with the appliance through the internet.

  • The client has several software components:
    • Visual Basic software to communicate with the server through WinSock, and to communicate with the user through a GUI.
    • The visual basic software contains the following components and may be downloaded at the bottom of this page as
      • stepper_control_client.frm
      • stepper_control_client.vbp
      • stepper_control_client.vbw
      • stepper_control_client.exe
    • ActiveX FTP Client component to allow the client to FTP image files from the server PC's FTP server. For this application, Distinct Software's ftpClient OCX was used.
  • Note, ActiveX components must be installed on a PC in addition to the Visual Basic Executable in order for the client to properly function.

Turbo C

To demonstrate the functionality of the circuit before investing time into writing the server and client software, I wrote some simple code in TurboC.
This code:
  • Prompts the user for the address of the 8255 card.
  • Prompts the user for the direction to rotate the motor.
  • Prompts the user for the number of degrees to rotate the motor.
  • Rotates the stepper motor in the desired direction by the desired amount.

You may download source and executable code at the bottom of this page below in

Theory of Operation

Here is how the system works:
  • Control data is passed between the server and client using WinSock TCP/IP.
  • Image data is passed from the server to the client using FTP. The client has ftp code built into it, but the server PC must be running third party FTP server software.
  • When a command is sent from the client to the server, the server determines what direction to control the X-Y table.
  • The server then sends commands to the 8255 card by way of the 8255.dll file.
  • Once the server has completed commanding the X-Y table to move, the server saves an image of the new scene.
  • This scene is written to a file on the server PC by means of the WebCam OCX ActiveX component.
  • Upon completion of writing the image file, the server sends a message to the client to indicate that the X-Y table has reached the desired position, and that a new image file is available.
  • When the client receives this message, it opens an FTP dialog with the server PC and retrieves the latest image file.
  • The client then displays the latest image file and allows the user through the client to send more control data.

Operating Procedure

These steps must be taken in the following order to successfully run the internet appliance:
  • Start Up Procedure:

    • Connect appliance to the server PC.
    • Confirm network connectivity between server and client PCs.
    • Determine IP address of server PC.
    • Start the FTP server on the server PC.
    • Start the Stepper Control Server software on the server PC.
    • Select the camera driver when the Stepper Control Server requests it.
    • Start the Stepper Control Client software on the client PC.
    • Enter the server PC's IP address when the Stepper Control Client software requests it.
    • Utilize appliance.
  • Shut Down Procedure:

    • Close Stepper Control Client software.
    • Close Stepper Control Server software.
    • Close FTP server software on server PC.


Utilization of internet appliances such as an internet adapted scanning electron microscope have many advantages. When a piece of equipment is networked through the internet, two main things happen:
  • Expensive and rare pieces of equipment may be brought closer to users all over the world. This allows those who can not justify purchasing equipment to use it after an agreement is made with a host organization.
  • Organizations considering purchase of expensive equipment may be able to diffuse the cost of the equipment through renting time on the machine over the internet. The customer base for this use is of course worldwide.


Thursday, March 1, 2018

Range finder with a Laser and a Webcam in Visual Basic 6.0

The author of this ingenious project is a scientist, namely Dr. Todd Danko. I found Dr. Danko to be connected with General Electric, Lockheed Martin and DARPA. Nice to have such people in the VB6 community. Like every VB6 programmer, it's fluent in C ++, Java and ASM. From here I will let his text explain the project. 


There are many off the shelf range finding components available including ultrasonic, infrared, and even laser rangefinders. All of these devices work well, but in the field of aerial robotics, weight is a primary concern. It is desirable to get as much functionality out of each component that is added to an air-frame. Miniature robotic rotor craft for example can carry about 100g of payload. It is possible to perform machine vision tasks such as obstacle identification and avoidance though the use of a webcam (or mini wireless camera interfaced to a computer via USB adapter). Better yet, two webcams can provide stereo machine vision thus improving obstacle avoidance because depth can be determined. The drawback of this of course is the addition of the weight of a second camera. This page describes how a mini laser pointer can be configured along with a single camera to provide mono-machine vision with range information.

Theory of Operation

The diagram below shows how projecting a laser dot onto a target that is in the field of view of a camera, the distance to that target may be calculated. The math is very simple, so this technique works very well for machine vision applications that need to run quickly.

So, here is how it works. A laser-beam is projected onto an object in the field of view of a camera. This laser beam is ideally parallel to the optical axis of the camera. The dot from the laser is captured along with the rest of the scene by the camera. A simple algorithm is run over the image looking for the brightest pixels. Assuming that the laser is the brightest area of the scene (which seems to be true for my dollar store laser pointer indoors), the dots position in the image frame is known. Then we need to calculate the range to the object based on where along the y axis of the image this laser dot falls. The closer to the center of the image, the farther away the object is.

As we can see from the diagram earlier in this section, distance (D) may be calculated:

Of course, to solve this equation, you need to know h, which is a constant fixed as the distance between your laser pointer and camera, and theta. Theta is calculated:

Put the two above equations together, we get:

OK, so the number of pixels from the center of the focal plane that the laser dot appears can just be counted from the image. What about the other parameters in this equation? We need to perform a calibration to derive these.

To calibrate the system, we will collect a series of measurements where I know the range to the target, as well as the number of pixels the dot is from the center of the image each time. This data is below:

Calibration Data
pixels from centeractual D (cm)

Using the following equation, we can calculate the actual angle based on the value of h as well as actual distance for each data point.

Now that we have a Theta_actual for each value, we can come up with a relationship that lets us calculate theta from the number of pixels from image center. I used a linear relationship (thus a gain and offset are needed). This seems to work well even though it does not account for the fact that the focal plane is a plane rather than curved at a constant radius around the center of the lens.

From my calibration data, I calculated:

Offset (ro) = -0.056514344 radians

Gain (rpc) = 0.0024259348 radians/pixel


I solved for calculated distances, as well as error from actual distance from the calibration data:

Actual and Calculated Range Data
pixels from centercalc D (cm)actual D (cm)% error


There are not a lot of parts in my sample range finder. I used a piece of cardboard to hold a laser pointer to a webcam so that the laser pointer points in a direction that is parallel to that of the camera. The parts seen below are laid out on a one inch grid for reference.

My assembled range finder looks like this:


I have written software two ways, one using visual c++ and the other using visual basic. You will probably find that the visual basic version of the software is much easier to follow than the vc++ code, but with everything, there is a tradeoff. The vc++ code can be put together for free (assuming that you have visual studio), while the vb code requires the purchase of a third party software package (also in addition to visual studio).

Visual Basic

The visual basic code that I have written is available as a package named at the bottom of this page. For this code to work, you will need the VideoOCX ActiveX component installed on your computer. The code that describes the functions found in the main form is seen below:

Private Sub exit_Click()
    ' only if running...
    If (Timer1.Enabled) Then
        Timer1.Enabled = False  'Stop Timer
    End If
End Sub

Private Sub Start_Click() 'Init VideoOCX Control, allocate memory and start grabbing
    If (Not Timer1.Enabled) Then
        Start.Caption = "Stop"
        ' Disable internal error messages in VideoOCX
        VideoOCX.SetErrorMessages False
        ' Init control
        If (Not VideoOCX.Init) Then
            ' Init failed. Display error message and end sub
            MsgBox VideoOCX.GetLastErrorString, vbOKOnly, "VideoOCX Error"
            ' Allocate memory for global image handle
            capture_image = VideoOCX.GetColorImageHandle
            ' result_image = VideoOCX_Processed.GetColorImageHandle
            Timer1.Enabled = True 'Start capture timer
            ' Start Capture mode
            If (Not VideoOCX.Start) Then
                ' Start failed. Display error message and end sub
                MsgBox VideoOCX.GetLastErrorString, vbOKOnly, "VideoOCX Error"
            End If
        End If
        Start.Caption = "Start"
        Timer1.Enabled = False  'Stop Timer
    End If
End Sub

Private Sub Timer1_Timer()
    ' Timer for capturing - handles videoOCXTools
    Dim matrix As Variant
    Dim height, width As Integer
    Dim r, c As Integer
    Dim max_r, max_c As Integer
    Dim max_red As Integer
    Dim gain, offset As Variant
    Dim h_cm As Variant
    Dim range As Integer
    Dim pixels_from_center As Integer
    ' Calibrated parameter for pixel to distance conversion
    gain = 0.0024259348
    offset = -0.056514344
    h_cm = 5.842
    max_red = 0
    ' Capture an image
    If (VideoOCX.Capture(capture_image)) Then
        ' VideoOCX.Show capture_image
        ' Matrix transformation initialization
        matrix = VideoOCX.GetMatrix(capture_image)
        height = VideoOCX.GetHeight
        width = VideoOCX.GetWidth
        ' Image processing code
        ' The laser dot should not be seen above the middle row (with a little pad)
        For r = height / 2 - 20 To height - 1
            ' Our physical setup is roughly calibrated to make the laser
            ' dot in the middle columns...dont bother lookng too far away
            For c = width / 2 - 25 To width / 2 + 24
                ' Look for the largest red pixel value in the scene (red laser)
                If (matrix(c, r, 2) > max_red) Then
                    max_red = matrix(c, r, 2)
                    max_r = r
                    max_c = c
                End If
            Next c
        Next r
        ' Calculate the distance for the laser dot from middle of frame
        pixels_from_center = max_r - 120

        ' Calculate range in cm based on calibrated parameters
        range = h_cm / Tan(pixels_from_center * gain + offset)

        ' Print laser dot position row and column to screen
        row_val.Caption = max_r
        col_val.Caption = max_c
        ' Print range to laser illuminated object to screen
        range_val.Caption = range
        ' Draw a red vertical line to intersect target
        For r = 0 To height - 1
            matrix(max_c, r, 2) = 255
        Next r
        ' Draw a red horizontal line to intersect target
        For c = 0 To width - 1
            matrix(c, max_r, 2) = 255
        Next c
        VideoOCX.ReleaseMatrixToImageHandle (capture_image)
    End If
    VideoOCX.Show capture_image

End Sub

Screen shots from this code can be seen below:

Visual C++

void CTripodDlg::doMyImageProcessing(LPBITMAPINFOHEADER lpThisBitmapInfoHeader)
 // doMyImageProcessing:  This is where you'd write your own image processing code
 // Task: Read a pixel's grayscale value and process accordingly

 unsigned int W, H;   // Width and Height of current frame [pixels]
 unsigned int    row, col;  // Pixel's row and col positions
 unsigned long   i;   // Dummy variable for row-column vector
 unsigned int max_row;  // Row of the brightest pixel
 unsigned int max_col;  // Column of the brightest pixel
        BYTE  max_val = 0;         // Value of the brightest pixel

 // Values used for calculating range from captured image data
 // these values are only for a specific camera and laser setup
 const double gain = 0.0024259348; // Gain Constant used for converting
      // pixel offset to angle in radians
 const double offset = -0.056514344; // Offset Constant
 const double h_cm = 5.842;  // Distance between center of camera and laser
        double  range;          // Calculated range 
 unsigned int pixels_from_center; // Brightest pixel location from center
      // not bottom of frame
 char  str[80];         // To print message
 CDC  *pDC;   // Device context need to print message

        W = lpThisBitmapInfoHeader->biWidth; // biWidth: number of columns
        H = lpThisBitmapInfoHeader->biHeight; // biHeight: number of rows
 for (row = 0; row < H; row++) {
  for (col = 0; col < W; col++) {

   // Recall each pixel is composed of 3 bytes
   i = (unsigned long)(row*3*W + 3*col);
   // If the current pixel value is greater than any other, 
                        // it is the new max pixel
   if (*(m_destinationBmp + i) >= max_val) 
    max_val = *(m_destinationBmp + i);
    max_row = row;
    max_col = col;
 // After each frame, reset max pixel value to zero
        max_val = 0;

 for (row = 0; row < H; row++) {
  for (col = 0; col < W; col++) {

   i = (unsigned long)(row*3*W + 3*col);
   // Draw a white cross-hair over brightest pixel in the output display
   if ((row == max_row) || (col == max_col)) 
    *(m_destinationBmp + i) = 
    *(m_destinationBmp + i + 1) = 
    *(m_destinationBmp + i + 2) = 255; 

 // Calculate distance of brightest pixel from center rather than bottom of frame
        pixels_from_center = 120 - max_row;

 // Calculate range in cm based on bright pixel location, and setup specific constants
 range = h_cm / tan(pixels_from_center * gain + offset);

 // To print message at (row, column) = (75, 580)
 pDC = GetDC(); 

 // Display frame coordinates as well as calculated range
 sprintf(str, "Max Value at x= %u, y= %u, range= %f cm    ",max_col, max_row, range);
 pDC->TextOut(75, 580, str);

My complete code for this project is available as a package named at the bottom of this page.  Note, to run this executable, you will need to have both qcsdk and the qc543 driver installed on your computer.  Sorry, but you are on your own to find both of these. Below are two examples of the webcam based laser range finder in action. Note how it looks like there are two laser dots in the second example. This "stray light" is caused by internal reflections in the camera. The reflected dot loses intensity as it bounces within the camera so it does not interfere with the algorithm that detects the brightest pixel in the image.

Future Work

One specific improvement that can be made to this webcam based laser range finder is to project a horizontal line rather than a dot onto a target. This way, we could calculate the range for each column of the image rather than just one column. Such a setup would be able to be used to locate areas of maximum range as places that a vehicle could steer towards. Likewise, areas of minimum range would be identified as obstacles to be avoided.