/// 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.
/// 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
/// 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
#include <drivers.h>
#include <gtest/gtest.h>
#include <sstream>
+#include <iostream>
using namespace FreeDV;
}
};
-// /// 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;
}
#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()
}
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 *