From: bruceperens Date: Sat, 18 Jan 2014 22:31:12 +0000 (+0000) Subject: The tone test driver works correctly, tests written and passed. X-Git-Url: http://git.whiteaudio.com/gitweb/?a=commitdiff_plain;h=ef5517fd53bf0cbf9c4374869997c562f338cab7;p=freetel-svn-tracking.git The tone test driver works correctly, tests written and passed. git-svn-id: https://svn.code.sf.net/p/freetel/code@1374 01035d8c-6547-0410-b346-abe4f91aad63 --- diff --git a/freedv-server/source/audio_sink.cpp b/freedv-server/source/audio_sink.cpp index a8f8b1e2..14d00421 100644 --- a/freedv-server/source/audio_sink.cpp +++ b/freedv-server/source/audio_sink.cpp @@ -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) { } diff --git a/freedv-server/source/drivers.h b/freedv-server/source/drivers.h index f922b395..71e91b6f 100644 --- a/freedv-server/source/drivers.h +++ b/freedv-server/source/drivers.h @@ -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 diff --git a/freedv-server/source/test/tone.cpp b/freedv-server/source/test/tone.cpp index c1c3f21d..0089a967 100644 --- a/freedv-server/source/test/tone.cpp +++ b/freedv-server/source/test/tone.cpp @@ -2,6 +2,7 @@ #include #include #include +#include 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; } diff --git a/freedv-server/source/tone.cpp b/freedv-server/source/tone.cpp index ca52fddd..21bde424 100644 --- a/freedv-server/source/tone.cpp +++ b/freedv-server/source/tone.cpp @@ -2,31 +2,123 @@ #include "drivers.h" #include +#include +#define _USE_MATH_DEFINES +#include 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 *