'Delphi and Discrete Signals: Getting a fixed volume signal with fixed silence

This is a follow up on another question I already asked here How can I play a single tone or custom wave with Delphi?

To make a long story short, I used MMSystem's waveOutWrite() to create a Discrete Signal, but it seems it's either not working as I expected or I'm not getting it.

I wrote two Discrete Signals to the buffer with fixed spaces, like this Samples[i] := round(vol*sin(2*Pi*AFreq*t));

Where the volume is 1000 for a Signal whereas the "Spaces" is some 0 vol signals.

This is what it looks like while I expected something that would like like |...| enter image description here

Am I getting the Signal stuff all wrong or is it that I'm not using the WinAPI properly? I expected each fixed signal to look the same on the graph

P.S: I'm using Windows' Stereo Mix so there are no interferences P.P.S: Here's the code segment for converting Binary to Signal

function CreateBinaryTone(BinaryString: String): TWaveformSamples;
var
  I: Integer;
  omega,
  dt: double;
  vol: double;
begin
  omega := 2*Pi*AFreq;
  dt := 1/Format.nSamplesPerSec;

  SetLength(Samples, Length(BinaryString));

  for I := 1 to Length(BinaryString) do
  begin
    { Discrete Time }
    Vol := StrToInt(BinaryString[I]) * 1000;
    Samples[I] := vol * sin(omega * dt * I);
  end;

  Result := Samples;
end;


Solution 1:[1]

Your bit time needs to be much longer than the period of your carrier (sine) frequency, and the sample rate also needs to be more than 2X the carrier frequency. So your sample generation loop may need to be much much longer.

Solution 2:[2]

I may be late posting on this thread, but, if the point is creating a function that generates discrete, yet continuous sine/cosine signal WITHOUT taking time into consideration (ex. RealTime opperations without predictable time limits) there is an easy, yet effective way to do this!

First of all, maths:

Our friend, Euler has provide us a way to describe a Sine and a Cosine, using complex numbers.

enter image description here

By adding a time variable in this equation, we can make it so it describes a sine and a cosine wave

Euler - Sin/Cos Wave

In this ecuatoin, our cosine wave is represented by the Real part of this complex number and our Cosine wave by its Imaginary part:

Euler - Actual Sin/Cos Presentation

Now, every time we increase this time variable, it's just like we are increasing the rotation by a small angle (let's call it theta or ?). That means our next step, sould have an angle of our previous step PLUS this small ? angle

Keep in mind that we will account ?t as the angle of our last step and ?, as the angle that needs to be added to our last step's angle, in order to generate the new step.

Euler - Next Step (1/2)

If we develop our left side of this equation, we will see something intresting going on there:

Euler - Next Step (2/2)

As we know, if a complex number z is equal with another complex number w, that means that their real and imaginary parts are also equal.

Complex Numbers Equality

Having that in mind, we see a very interesting conclusion: If we have already calculated our last step's Sine and Cosine, we can easily calculate the next step by adding this ? angle, using the equations below for cosine and sine accordingly:

Euler - Next Step Calculation Equations

Finaly, Coding Section

Now, in order to create such function you will need some things:

  1. 2 global, Double variables, presenting last step's sin and cos
  2. An init function that sets the initial "step zero" and its initial phase
  3. A function that calculates and results the next step, when it's being called.

so 1st of all, declarations:

private
    [...]
    sine_last_cos: double;
    sine_last_sin: double;
    cosine_last_cos: double;
    cosine_last_sin: double;
    
    procedure SineInit(starting_phase: double);
    procedure CosineInit(starting_phase: double);
    function SineGen(amplitutde: double; sampling_rate: Integer; Freq: Double): double;
    function CosineGen(amplitutde: double; sampling_rate: Integer; Freq: Double): double;

Next goes implementations:

procedure Tmain.SineInit(starting_phase: double);
begin
    sine_last_sin := sin(starting_phase*pi/180);   //given phase is in deg, not rad
    sine_last_cos := cos(starting_phase*pi/180);
end;

procedure Tmain.CosineInit(starting_phase: double);
begin
    cosine_last_sin := sin(starting_phase*pi/180);
    cosine_last_cos := cos(starting_phase*pi/180);
end;

function Tmain.SineGen(amplitutde: double; sampling_rate: Integer; Freq: Double): double;
var sin_theta: double;
    cos_theta: double;
    new_sin_step, new_cos_step: double;
begin
    sin_theta :=  sin(2*pi*Freq/sampling_rate);    //theta depend on sampling freq
    cos_theta :=  cos(2*pi*Freq/sampling_rate);    //as well as desirable freq
    new_cos_step:= sine_last_cos*cos_theta - sine_last_sin*sin_theta;
    new_sin_step:= sine_last_cos*sin_theta + sine_last_sin*cos_theta;
    sine_last_sin:= new_cos_step;
    sine_last_cos:= new_sin_step;
    result := amplitutde *new_sin_step;
end;

function Tmain.CosineGen(amplitutde: double; sampling_rate: Integer; Freq: Double): double;
var sin_theta: double;
    cos_theta: double;
    new_sin_step, new_cos_step: double;
begin
    sin_theta :=  sin(2*pi*Freq/sampling_rate);    //theta depend on sampling freq
    cos_theta :=  cos(2*pi*Freq/sampling_rate);    //as well as desirable freq
    new_cos_step:= cosine_last_cos*cos_theta - cosine_last_sin*sin_theta;
    new_sin_step:= cosine_last_cos*sin_theta + cosine_last_sin*cos_theta;
    cosine_last_sin:= new_cos_step;
    cosine_last_cos:= new_sin_step;
    result := amplitutde *new_sin_step;
end;

In this example, Init procedures should be called every time the program launches (aka before the first SineGen/CosineGen call) and EVERY time you want to reset your wave.

EDIT: Corrected images + added the forgotten Init Procedures

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2