patterncppMinor
Basic C++ tone generator
Viewed 0 times
generatorbasictone
Problem
I'm trying to learn C++ (with experience in C and Java, inter alia).
I wrote a program to output a waveform of multiple (superimposed) tones, each varying in pitch.
To execute:
What can I improve in terms of C++ style and idioms?
I tried to use RAII for my streams and collections, and I tried to avoid
A couple specific questions:
If not, what's the standard C++ way to write binary data to a file?
Is
Is this okay? What would be better?
Tone.hpp
Tone.cpp
main.cpp
```
#include
#include
#include
#include
#include "Tone.hpp"
void write_sample(std::ostream &stream, double sample);
int main()
{
constexpr double SAMPLE_RATE = 44100;
std::vector ton
I wrote a program to output a waveform of multiple (superimposed) tones, each varying in pitch.
To execute:
- Compilation:
g++ -W -Wall -pedantic -std=c++11 main.cpp Tone.cpp
- Running:
./a.out
- Playing:
aplay out -r 44100 -f S16_LE(orS16_BEif you're on a big-endian system)
What can I improve in terms of C++ style and idioms?
I tried to use RAII for my streams and collections, and I tried to avoid
news and pointers.A couple specific questions:
- In
write_sample, are the cast to(char *)and thesizeofdivisions okay?
If not, what's the standard C++ way to write binary data to a file?
Is
sizeof really even used in C++, or is this a discouraged C artifact?- What should be
constvs.constexpr?
- I used a C-style array for
octavesin main, instead ofstd::array, because I like the automatic initializer counting and the iteration is easier.
Is this okay? What would be better?
- Does C++ use
size_t,int16_t, and the like?
Tone.hpp
#ifndef TONE_H
#define TONE_H
class Tone
{
private:
const double sampleRate;
double s; /* current position along the unit sine curve */
double lastSample;
public:
double getSample() const;
double nextSample(double frequency, double amplitude);
Tone(double sampleRate);
};
#endifTone.cpp
#include
#include "Tone.hpp"
Tone::Tone(double sampleRate) : sampleRate(sampleRate), s(0), lastSample(0)
{
}
double Tone::getSample() const
{
return this->lastSample;
}
double Tone::nextSample(double frequency, double amplitude)
{
this->s += frequency / this->sampleRate;
this->lastSample = amplitude * sin(2*M_PI * this->s);
return this->lastSample;
}main.cpp
```
#include
#include
#include
#include
#include "Tone.hpp"
void write_sample(std::ostream &stream, double sample);
int main()
{
constexpr double SAMPLE_RATE = 44100;
std::vector ton
Solution
Overall it looks fine for a newcomer, you've made proper use of
A few points you can still work on:
-
For such a tiny class like
-
Prefixing in-class member access with
-
This is a personal preference, I find it better to declare public members of a class first, followed by protected and then private. My rationale is that the public section should have more emphasis, since it is what readers and users of my code will be interested on most of the time. Private/protected details are of interest for the implementer/maintainer, so they don't need to be visible right at the top of the file.
-
When using `
const for variables and methods, which is something beginners tend to overlook.A few points you can still work on:
-
For such a tiny class like
Tone, I would not bother separating declaration from implementation and would have declared it inline in the header file only. That would be less boilerplate and easier to maintain. A word of caution though: Inline code can be better optimized by the compiler and is recommend for small methods (like get/set accessors), but don't overuse it. Too much inlining will increase compile-times and generate bloated executables. I don't recommend it as an optimization either, the gain is in reducing redundant/boilerplate code implicit in the prototype/implementation setup.-
Prefixing in-class member access with
this-> is very rare in C++. You should only be doing that when, for instance, you need to disambiguate some shadowed member access (but even that is discouraged because shadowing members is a bit of a design flaw; better to uniquely identify your variables).-
This is a personal preference, I find it better to declare public members of a class first, followed by protected and then private. My rationale is that the public section should have more emphasis, since it is what readers and users of my code will be interested on most of the time. Private/protected details are of interest for the implementer/maintainer, so they don't need to be visible right at the top of the file.
-
When using `
, the correct way of referencing its functions, like sin/pow, is thru the std:: namespace. They happen to visible at the global level because on most compilers math.h (the C header) and cmath are implemented as wrappers to the same file. There is no requirement from the C++ Standard that this be implemented is such way, so a C++ compiler is not required to provide sin() outside the std namespace when including cmath.
-
Avoid using C-style casts. To cast numerical values, C++ provides static_cast. To cast pointers, reinterpret_cast is usually the operator to use. A quick overview on C++ cast operators.
-
Yes, size_t and int16_t are part of the Standard and are declared in and respectively. For ultimate correctness, they should be accessed from the std namespace, (e.g.: std::size_t, std:int16_t) for the same reasons cited on §4.
-
Your main() function is too long and doing too much. You should probably have another class, Sample perhaps, that holds the array of Tones. That class should provide methods for processing and have write_sample() as a member.
-
There is a much simpler way of initializing the tones vector. You can fill it with defaults on declaration using a parameterized constructor and eliminate the loop:
std::vector tones(count, Tone(SAMPLE_RATE));
-
If you use an std::array you gain the size() method that knows the array length, which would remove the need for the sizeof() calculation, which is not the clearest thing you can do. Another way I tend to use for inferring the length of statically allocated arrays, without compromising readability, is by declaring a helper template function:
// Length in elements of type 'T' of statically allocated C++ arrays.
template
constexpr std::size_t arrayLength(const T (&)[N])
{
return N;
}
...
const double octaves[] = { -2, 0, 4./12, 7./12, 1, 16./12, 19./12 };
const auto count = arrayLength(octaves);
constexpr` will force the function to be evaluated at compile time, thus making it suitable for use in places a compile-time constant would be required.Code Snippets
std::vector<Tone> tones(count, Tone(SAMPLE_RATE));// Length in elements of type 'T' of statically allocated C++ arrays.
template<class T, std::size_t N>
constexpr std::size_t arrayLength(const T (&)[N])
{
return N;
}
...
const double octaves[] = { -2, 0, 4./12, 7./12, 1, 16./12, 19./12 };
const auto count = arrayLength(octaves);Context
StackExchange Code Review Q#83504, answer score: 8
Revisions (0)
No revisions yet.