The tone test driver works correctly, tests written and passed.
authorbruceperens <bruceperens@01035d8c-6547-0410-b346-abe4f91aad63>
Sat, 18 Jan 2014 22:31:12 +0000 (22:31 +0000)
committerbruceperens <bruceperens@01035d8c-6547-0410-b346-abe4f91aad63>
Sat, 18 Jan 2014 22:31:12 +0000 (22:31 +0000)
git-svn-id: https://svn.code.sf.net/p/freetel/code@1374 01035d8c-6547-0410-b346-abe4f91aad63

freedv-server/source/audio_sink.cpp
freedv-server/source/drivers.h
freedv-server/source/test/tone.cpp
freedv-server/source/tone.cpp

index a8f8b1e2b45a5a4650a8ae3ff5be451182b41137..14d004215db22c260766940f1db0f727a1d5376a 100644 (file)
@@ -13,11 +13,11 @@ namespace FreeDV {
 
        /// Get the current audio level, normalized to the range of 0.0 to 1.0.
        virtual float
-               level();
+               amplitude();
 
        /// Set the current audio level within the range of 0.0 to 1.0.
        virtual void
-               level(float value);
+               amplitude(float value);
 
         /// Write audio into the "short" type.
        virtual std::size_t
@@ -34,13 +34,13 @@ namespace FreeDV {
   }
 
   float
-  AudioSink::level()
+  AudioSink::amplitude()
   {
     return 0;
   }
 
   void
-  AudioSink::level(float value)
+  AudioSink::amplitude(float value)
   {
   }
 
index f922b395db5cc871df17ef77330520ad3f8900d2..71e91b6f0ed93f4a57db21c4d8c47b8deb33bced 100644 (file)
@@ -6,6 +6,13 @@
 /// Namespace used for all code in this program.
 namespace FreeDV {
 
+  /// The sample rate used by all audio interfaces in the program.
+  /// the sound cards are in general driven at 48000 because that's
+  /// the only reliable sample rate they all have in common. SampleRate
+  /// may be lower than that and thus there may be resampling in the
+  /// drivers.
+  const unsigned int   SampleRate = 8000;
+
   /// Allocate memory and copy a string into it, so that it is permanently
   /// stored.
   /// \param s The string to be copied.
@@ -91,12 +98,12 @@ namespace FreeDV {
        /// Get the current audio level.
        /// \return The current audio level.
        /// The value is normalized to the range of 0.0 to 1.0.
-       virtual float   level() = 0;
+       virtual float   amplitude() = 0;
 
        /// Set the current audio level.
        /// \param value The new value for the current audio level.
        /// The value must be normalized within the range of 0.0 to 1.0.
-       virtual void    level(float value) = 0;
+       virtual void    amplitude(float value) = 0;
 
         /// Read audio into an array of the signed 16-bit integer type.
        virtual std::size_t
@@ -118,11 +125,11 @@ namespace FreeDV {
 
        /// Get the current audio level.
        /// The value is normalized to the range of 0.0 to 1.0.
-       virtual float   level() = 0;
+       virtual float   amplitude() = 0;
 
        /// Set the current audio level.
        /// The value must be within the range of 0.0 to 1.0.
-       virtual void    level(float value) = 0;
+       virtual void    amplitude(float value) = 0;
 
         /// Write audio from an array of the signed 16-bit integer type.
        virtual std::size_t
index c1c3f21d981c52fd75e59013dac0928f3c42e750..0089a967a93497098138c25508637ae1150f666b 100644 (file)
@@ -2,6 +2,7 @@
 #include <drivers.h>
 #include <gtest/gtest.h>
 #include <sstream>
+#include <iostream>
 
 using namespace FreeDV;
 
@@ -23,40 +24,148 @@ protected:
   }
 };
 
-//     /// Destroy an Tone device instance.
-//     virtual         ~Tone() = 0;
-//
-//     /// Get the current audio level.
-//     /// \return The current audio level.
-//     /// The value is normalized to the range of 0.0 to 1.0.
-//     virtual float   level() = 0;
-//
-//     /// Set the current audio level.
-//     /// \param value The new value for the current audio level.
-//     /// The value must be normalized within the range of 0.0 to 1.0.
-//     virtual void    level(float value) = 0;
-//
-//        /// Read audio into an array of the signed 16-bit integer type.
-//     virtual std::size_t
-//                     read16(int16_t * array, std::size_t length) = 0;
-
 TEST_F(ToneTest, InitialLevelIs1) {
-  EXPECT_EQ(1.0, i->level());
+  EXPECT_EQ(1.0, i->amplitude());
 }
 
 TEST_F(ToneTest, LevelChange) {
-  i->level(0.0);
-  EXPECT_EQ(0.0, i->level());
-  i->level(0.5);
-  EXPECT_EQ(0.5, i->level());
-  i->level(1.0);
-  EXPECT_EQ(1.0, i->level());
+  i->amplitude(0.0);
+  EXPECT_EQ(0.0, i->amplitude());
+  i->amplitude(0.5);
+  EXPECT_EQ(0.5, i->amplitude());
+  i->amplitude(1.0);
+  EXPECT_EQ(1.0, i->amplitude());
 }
 
 TEST_F(ToneTest, EnforcesNormalization) {
-  EXPECT_THROW(i->level(-1e-7), std::runtime_error);
-  EXPECT_THROW(i->level(1.0 + 1e-7), std::runtime_error);
-  EXPECT_NO_THROW(i->level(0.0));
-  EXPECT_NO_THROW(i->level(0.5));
-  EXPECT_NO_THROW(i->level(1.0));
+  EXPECT_THROW(i->amplitude(-1e-7), std::runtime_error);
+  EXPECT_THROW(i->amplitude(1.0 + 1e-7), std::runtime_error);
+  EXPECT_NO_THROW(i->amplitude(0.0));
+  EXPECT_NO_THROW(i->amplitude(0.5));
+  EXPECT_NO_THROW(i->amplitude(1.0));
+}
+
+// Maximum positive value of a signed 16-bit integer.
+const int16_t  MaxS16((1 << 15) - 1);
+// Half of the maximum positive value of a signed 16-bit integer.
+const int16_t  HalfS16(1 << 14);
+
+TEST(ToneTest1, AtNyquistLimit) {
+  int16_t      buffer[4];
+
+  std::stringstream    stream;
+
+  stream << SampleRate / 2 << ",1";
+
+  AudioInput *         i = Driver::Tone(stream.str().c_str());
+
+  i->read16(buffer, sizeof(buffer) / sizeof(*buffer));
+
+  EXPECT_EQ(MaxS16, buffer[0]);
+  EXPECT_EQ(-MaxS16, buffer[1]);
+  EXPECT_EQ(MaxS16, buffer[2]);
+  EXPECT_EQ(-MaxS16, buffer[3]);
+
+  delete i;
+}
+
+TEST(ToneTest1, AtHalfNyquistLimit) {
+  int16_t      buffer[8];
+
+  std::stringstream    stream;
+
+  stream << SampleRate / 4 << ",1";
+
+  AudioInput *         i = Driver::Tone(stream.str().c_str());
+
+  i->read16(buffer, sizeof(buffer) / sizeof(*buffer));
+
+  EXPECT_EQ(MaxS16, buffer[0]);
+  EXPECT_EQ(0, buffer[1]);
+  EXPECT_EQ(-MaxS16, buffer[2]);
+  EXPECT_EQ(0, buffer[3]);
+  EXPECT_EQ(MaxS16, buffer[4]);
+  EXPECT_EQ(0, buffer[5]);
+  EXPECT_EQ(-MaxS16, buffer[6]);
+  EXPECT_EQ(0, buffer[7]);
+
+  delete i;
+}
+
+TEST(ToneTest1, FrequencyIsZero) {
+  int16_t      buffer[4];
+
+  AudioInput *         i = Driver::Tone("0,1");
+
+  i->read16(buffer, sizeof(buffer) / sizeof(*buffer));
+
+  EXPECT_EQ(0, buffer[0]);
+  EXPECT_EQ(0, buffer[1]);
+  EXPECT_EQ(0, buffer[2]);
+  EXPECT_EQ(0, buffer[3]);
+
+  delete i;
+}
+
+TEST(ToneTest1, AmplitudeIsZero) {
+  int16_t      buffer[4];
+
+  AudioInput *         i = Driver::Tone("1000,0");
+
+  i->read16(buffer, sizeof(buffer) / sizeof(*buffer));
+
+  EXPECT_EQ(0, buffer[0]);
+  EXPECT_EQ(0, buffer[1]);
+  EXPECT_EQ(0, buffer[2]);
+  EXPECT_EQ(0, buffer[3]);
+
+  delete i;
+}
+
+TEST(ToneTest1, WavesSumCorrectly) {
+  int16_t      buffer[8];
+
+  std::stringstream    stream;
+
+  stream << SampleRate / 2 << ",0.5:";
+  stream << SampleRate / 4 << ",0.5";
+
+  AudioInput *         i = Driver::Tone(stream.str().c_str());
+
+  i->read16(buffer, sizeof(buffer) / sizeof(*buffer));
+
+  EXPECT_EQ(MaxS16, buffer[0]);
+  EXPECT_EQ(-HalfS16, buffer[1]);
+  EXPECT_EQ(0, buffer[2]);
+  EXPECT_EQ(-HalfS16, buffer[3]);
+  EXPECT_EQ(MaxS16, buffer[4]);
+  EXPECT_EQ(-HalfS16, buffer[5]);
+  EXPECT_EQ(0, buffer[6]);
+  EXPECT_EQ(-HalfS16, buffer[7]);
+
+  delete i;
+}
+
+TEST(ToneTest1, SumOfAmplitudesIsNormalizedCorrectly) {
+  int16_t      buffer[8];
+
+  std::stringstream    stream;
+
+  stream << SampleRate / 2 << ",1:";
+  stream << SampleRate / 4 << ",1";
+
+  AudioInput *         i = Driver::Tone(stream.str().c_str());
+
+  i->read16(buffer, sizeof(buffer) / sizeof(*buffer));
+
+  EXPECT_EQ(MaxS16, buffer[0]);
+  EXPECT_EQ(-HalfS16, buffer[1]);
+  EXPECT_EQ(0, buffer[2]);
+  EXPECT_EQ(-HalfS16, buffer[3]);
+  EXPECT_EQ(MaxS16, buffer[4]);
+  EXPECT_EQ(-HalfS16, buffer[5]);
+  EXPECT_EQ(0, buffer[6]);
+  EXPECT_EQ(-HalfS16, buffer[7]);
+
+  delete i;
 }
index ca52fddd85efefb6a4f0cd3054a6a95f7eab9e8f..21bde424e8441d5c34c434652f23b906dd8932cb 100644 (file)
 
 #include "drivers.h"
 #include <stdexcept>
+#include <sstream>
+#define _USE_MATH_DEFINES
+#include <cmath>
 
 namespace FreeDV {
   /// This is a test driver that provides tones.
   class Tone : public AudioInput {
   private:
-    float              volume;
+    float              master_amplitude;
+    unsigned int       clock;
+
+    struct tone_info {
+      float    frequency;
+      float    amplitude;
+    };
+
+    tone_info  tones[101];
+
+    /// Generate a sine wave.
+    float
+    sine_wave(float frequency, unsigned int step)
+    {
+      // The number of samples per cycle for the given frequency and sample
+      // rate.
+      const float      samplesPerCycle = SampleRate / frequency;
+
+      // Add 1/4 turn to the step so that at the Nyquist frequency we
+      // render the peaks rather than the zero crossings.
+      const float      bias = step + (samplesPerCycle / 4);
+
+      // Angular measure where 1.0 = 360 degrees.
+      const float      portionOfCycle = fmod(bias, samplesPerCycle) / samplesPerCycle;
+
+      // Convert to radians.
+      const float radians = portionOfCycle * M_PI * 2;
+
+      const float sine = sin(radians);
+
+      return sine;
+    }
 
   public:
     /// Instantiate the tone driver.
                        Tone(const char * parameter);
     virtual            ~Tone();
     
-    // Get the current audio level, normalized to the range of 0.0 to 1.0.
-    virtual float      level();
+    // Get the current audio amplitude, normalized to the range of 0.0 to 1.0.
+    virtual float      amplitude();
     
-    // Set the current audio level within the range of 0.0 to 1.0.
-    virtual void       level(float value);
+    // Set the current audio amplitude within the range of 0.0 to 1.0.
+    virtual void       amplitude(float value);
     
     // Read audio into the "short" type.
     virtual std::size_t        read16(int16_t * array, std::size_t length);
   };
 
+  void
+  breakme()
+  {
+  }
+
   Tone::Tone(const char * parameters)
-  : AudioInput("tone", parameters), volume(1.0)
+  : AudioInput("tone", parameters), clock(0), master_amplitude(1.0)
   {
+    unsigned int       index = 0;
+    unsigned int       input = 0;
+    std::stringstream  p(parameters);
+
+    breakme();
+
+    while ( !p.eof() ) {
+      float frequency = 0.0;
+      float amplitude = 1.0;
+
+      if ( index >= ((sizeof(tones) / sizeof(*tones)) - 1) ) {
+        std::cerr << "Too many tones, number " << index
+         << " and subsequent were discarded." << std::endl;
+      }
+      p >> frequency;
+      if ( !p.fail() ) {
+        char c;
+       p >> c;
+        if ( !p.fail() && c == ',' ) {
+          p >> amplitude;
+          if ( !p.fail() ) {
+           c = '\0';
+            p >> c;
+          }
+        }
+        if ( !p.eof() && !p.fail() && c != ':' ) {
+          std::cerr << "tone: bad character \"" << c << "\" in tone string at tone "
+           << input << '.' << std::endl;
+          break;
+        }
+      }
+      if ( frequency == 0.0 || amplitude == 0.0 ) {
+        input++;
+        continue;
+      }
+      if ( frequency < 0.0 || frequency > SampleRate ) {
+        std::cerr << "tone: frequency must be in range of 0.0.."
+         << SampleRate / 2 << ", is "
+         << frequency << '.' << std::endl;
+       break;
+      }
+      if ( amplitude < 0.0 || amplitude > 1.0 ) {
+        std::cerr << "tone: amplitude must be in range of 0.0..1.0, is "
+         << frequency << '.' << std::endl;
+       break;
+      }
+      tones[index].frequency = frequency;
+      tones[index].amplitude = amplitude;
+      index++;
+      input++;
+    }
+    tones[index].frequency = 0.0;
+    tones[index].amplitude = 0.0;
   }
 
   Tone::~Tone()
@@ -34,24 +126,42 @@ namespace FreeDV {
   }
 
   float
-  Tone::level()
+  Tone::amplitude()
   {
-    return volume;
+    return master_amplitude;
   }
 
   void
-  Tone::level(float value)
+  Tone::amplitude(float value)
   {
     if ( value < 0.0 || value > 1.0 )
       throw std::runtime_error(
-       "Level set to value outside of the range 0.0..1.0");
-    volume = value;
+       "Amplitude set to value outside of the range 0.0..1.0");
+    master_amplitude = value;
   }
 
   std::size_t
   Tone::read16(int16_t * array, std::size_t length)
   {
-    return 0;
+    const unsigned int array_length = ((sizeof(tones) / sizeof(*tones)) - 1);
+
+    for ( unsigned int i = 0; i < length; i++ ) {
+      float value = 0;
+      float sumOfAmplitudes = 0;
+      for ( unsigned int j = 0; j < array_length && tones[j].amplitude > 0.0;
+       j++ ) {
+        value += (sine_wave(tones[j].frequency, i + clock)
+         * tones[j].amplitude);
+       // FIX: Hoist this out of the inner loop after it's tested.
+        sumOfAmplitudes += tones[j].amplitude;
+      }
+      // If the sum of amplitudes is greater than 1.0, normalize so that the
+      // sum of amplitudes is 1.0.
+      if ( sumOfAmplitudes > 1.0 )
+        value /= sumOfAmplitudes;
+      array[i] = (int16_t)rint((value * ((1 << 15) - 1)));
+    }
+    clock = (clock + length) % SampleRate;
   }
 
   AudioInput *