445 lines
16 KiB
C++
445 lines
16 KiB
C++
// XTronical DAC Audio Library, currently supporting ESP32
|
|
// May work with other processors and/or DAC's with or without modifications
|
|
// (c) XTronical 2018, Licensed under GNU GPL 3.0 and later, under this license absolutely no warranty given
|
|
// See www.xtronical.com for documentation
|
|
// This software is currently under active development (Jan 2018) and may change and break your code with new versions
|
|
// No responsibility is taken for this. Stick with the version that works for you, if you need newer commands from later versions
|
|
// you will have to alter your original code to work with the new if required.
|
|
|
|
|
|
#pragma once
|
|
#include "MusicDefinitions.h"
|
|
|
|
#define BUFFER_SIZE_DEFAULT 4000 // Size of buffer to store data to send to DAC. 3 bytes minimum!
|
|
// 4000 bytes (Default) should allow for very slow main loops
|
|
// that repeat only about every 100Hz, If your main loop is much faster
|
|
// than this and the VAST majority will be you can safely reduce this size
|
|
// without any issues down to an absolute minimum of 3 bytes
|
|
// If you experience sound slowing down then you need to increase
|
|
// Buffer memory. Which can be done in your code when you create the
|
|
// DacAudio object
|
|
// If your main loop is very very slow but you want to keep buffer
|
|
// memory low then consider calling the DAC_Audio_Class FillBuffer
|
|
// routine more than once in you main loop. Although to be affective
|
|
// they should not be called in quick succession but farther apart in
|
|
// time on your main loop.
|
|
// Use the routine MaxBufferUsage in your main loop to output the max
|
|
// buffer memory (via serial monitor) that your loop is using, you can
|
|
// then set you buffer memory very precisely
|
|
#define BytesPerSec 50000 // The rate at which bytes are sent to the DAC, note that the max
|
|
// sample rate however is 44100Khz, this higher sampling rate allows
|
|
// for samples to be increased in pitch. If a 44100 rate then the max
|
|
// would be 3 x the pitch.
|
|
|
|
|
|
uint8_t SetVolume(uint8_t Volume); // returns the sound byte value adjusted for the volume passed
|
|
|
|
class XT_Envelope_Class; // forward ref for this class
|
|
|
|
|
|
class XT_Filter_Class
|
|
{
|
|
// base class for a filter, a filter is a device that manipulates the wave form
|
|
// after it has been produced. Various are available inheriting from this class
|
|
public:
|
|
virtual uint8_t FilterWave(uint8_t TheByte);
|
|
};
|
|
|
|
class XT_FilterNoise_Class : public XT_Filter_Class
|
|
{
|
|
// adds noise to a wave form, use min and max range to set lower and upper_bound
|
|
// maximums for the noise. The value is added or removed from the passed in value
|
|
public:
|
|
int8_t Min=-2; // These defaults have a range of 4 from -2 to +2
|
|
int8_t Max=2;
|
|
|
|
XT_FilterNoise_Class(int8_t Min,int8_t Max);
|
|
XT_FilterNoise_Class(int8_t AmountOfNoise);
|
|
|
|
uint8_t FilterWave(uint8_t TheByte);
|
|
|
|
};
|
|
|
|
class XT_PlayListItem_Class
|
|
{
|
|
private:
|
|
|
|
protected:
|
|
|
|
public:
|
|
// These are the linked list items for the sounds to play through the DAC:
|
|
// Will be of use when multiple sounds can play at once using mixing
|
|
XT_PlayListItem_Class *PreviousItem=0;
|
|
XT_PlayListItem_Class *NextItem=0;
|
|
XT_Filter_Class *Filter;
|
|
|
|
bool Playing=false; // true if currently being played else false
|
|
bool NewSound; // When first set to play this will be true, once this
|
|
// sound has started sending bytes to the buffer it will be false
|
|
// Helps when setting NextFillPos (below) to initial value
|
|
uint32_t PlayingTime; // Total playing time of this item in 1000's of a second
|
|
uint32_t TimeElapsed; // Total playing time elapsed so far in 1000's of a second
|
|
uint32_t TimeLeft; // Total playing time left so far in 1000's of a second
|
|
|
|
uint32_t NextFillPos; // Next fill position in buffer to fill, because we have
|
|
// a large buffer, all sounds need their individual fill positions
|
|
// because you could be playing one sound which has filled perhaps
|
|
// 2 seconds worth of buffer (but not yet played those 2 s)
|
|
// and then mix in another. In this situation the bytes from the new
|
|
// sound must be mixed at the current playing point. If you did
|
|
// not do this then your new sound would not start until about
|
|
// 2 seconds later and not when you intended.
|
|
|
|
char Name[32]; // Name of this sound - optional, used mostly in debugging
|
|
uint8_t LastValue; // Last value returned from NextByte function below
|
|
uint8_t Volume; // volume 0 min, 127 max
|
|
|
|
bool RepeatForever; // if true repeats forever, if true value for repeat below ignored
|
|
uint16_t Repeat; // Number of times to repeat, 1 to 65535
|
|
uint16_t RepeatIdx; // internal use only, do not use, should put this in protected or
|
|
// private really and have an access function, do in future
|
|
|
|
|
|
virtual uint8_t NextByte(); // to be overridden by any descendants
|
|
virtual void Init(); // initialize any default values
|
|
|
|
XT_PlayListItem_Class();
|
|
};
|
|
|
|
|
|
|
|
// The Main Wave class for sound samples
|
|
class XT_Wav_Class : public XT_PlayListItem_Class
|
|
{
|
|
private:
|
|
protected:
|
|
|
|
public:
|
|
uint16_t SampleRate;
|
|
uint32_t DataSize=0; // The last integer part of count
|
|
uint32_t DataStart; // offset of the actual data.
|
|
uint32_t DataIdx;
|
|
const unsigned char *Data;
|
|
float IncreaseBy=0; // The amount to increase the counter by per call to "onTimer"
|
|
float Count=0; // The counter counting up, we check this to see if we need to send
|
|
// a new byte to the buffer
|
|
int32_t LastIntCount=-1; // The last integer part of count
|
|
float SpeedUpCount=0; // The decimal part of the amount to increment the dataptr by for the
|
|
// next byte to send to the DAC. As we cannot move through data in
|
|
// partial amounts then we keep track of the decimal and when it
|
|
// "clicks over" to a new integer we increment the dataptr an extra
|
|
// "1" place.
|
|
float Speed=1; // 1 is normal, less than 1 slower, more than 1 faster, i.e. 2
|
|
// is twice as fast, 0.5 half as fast., use the getter and setter
|
|
// functions to access. default is 1, normal speed
|
|
|
|
// constructors
|
|
XT_Wav_Class(const unsigned char *WavData);
|
|
|
|
// functions
|
|
uint8_t NextByte()override;
|
|
void Init()override; // initialize any default values
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Envelope class for instruments to use, see www.xtronical.com for details
|
|
class XT_EnvelopePart_Class
|
|
{
|
|
// Definition of a single part of a sound envelope
|
|
|
|
private:
|
|
uint16_t Duration; // How long this part plays for in 1000's of a second
|
|
|
|
public:
|
|
int8_t StartVolume=-1; // if 0-127 then this is volume to start this part at
|
|
uint8_t TargetVolume; // volume to reach in this part 0-127
|
|
uint32_t RawDuration; // duration ins 50,000's of a second
|
|
bool Completed;
|
|
|
|
XT_EnvelopePart_Class *PreviousPart=0;
|
|
XT_EnvelopePart_Class *NextPart=0;
|
|
|
|
// functions
|
|
void SetDuration(uint16_t Duration);
|
|
uint16_t GetDuration();
|
|
|
|
};
|
|
|
|
|
|
class XT_Envelope_Class
|
|
{
|
|
// Envelope definition contains basically a linked list of Envelope parts
|
|
// Using the envelope class you can have basic ADSR envelopes or more complex (or even simpler) ones
|
|
// You can also have multiple envelopes per sound, so as one completes the next in list will start
|
|
// This easily enables repeats of one envelope followed by a different one
|
|
|
|
public:
|
|
XT_EnvelopePart_Class *FirstPart=0; // start of linked list of envelope parts
|
|
XT_EnvelopePart_Class *LastPart=0; // start of linked list of envelope parts
|
|
XT_EnvelopePart_Class *CurrentEnvelopePart=0; // Current active envelope part
|
|
|
|
XT_Envelope_Class *NextEnvelope=0; // Next envelope of any
|
|
uint8_t Repeats=0; // number of repeats
|
|
uint8_t RepeatCounter; // current repeat count used in code
|
|
uint32_t DurationCounter; // counts down until 0 for next envelope part
|
|
float VolumeIncrement=0; // amount to increase volume by per sample
|
|
float CurrentVolume=0;
|
|
bool EnvelopeCompleted; // Envelope completed
|
|
|
|
XT_Envelope_Class();
|
|
~XT_Envelope_Class();
|
|
void Init();
|
|
XT_EnvelopePart_Class* AddPart(uint16_t Duration,uint16_t TargetVolume); // 0 to Max 127
|
|
XT_EnvelopePart_Class* AddPart(uint16_t Duration,uint16_t StartVolume,uint16_t TargetVolume); // 0 to Max 127
|
|
void InitEnvelopePart(XT_EnvelopePart_Class* EPart,uint8_t LastVolume);
|
|
uint8_t NextByte(uint8_t ByteToPlay);
|
|
};
|
|
|
|
|
|
|
|
|
|
class XT_Wave_Class
|
|
{
|
|
// Base wave class for all others to inherit from
|
|
// Every Instrument must have a wave form class, by default if none specified
|
|
// then uses square
|
|
|
|
protected:
|
|
uint8_t CurrentByte=255;
|
|
float ChangeAmplitudeBy;
|
|
float NextAmplitude;
|
|
float CounterStartValue;
|
|
float Counter;
|
|
|
|
public:
|
|
uint16_t Frequency; // Note frequency
|
|
virtual uint8_t NextByte();
|
|
virtual void Init(int8_t Note);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class XT_SquareWave_Class : public XT_Wave_Class
|
|
{
|
|
// Square wave class
|
|
public:
|
|
uint8_t NextByte();
|
|
void Init(int8_t Note);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class XT_TriangleWave_Class : public XT_Wave_Class
|
|
{
|
|
// Square wave class
|
|
public:
|
|
uint8_t NextByte();
|
|
void Init(int8_t Note);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class XT_SawToothWave_Class : public XT_Wave_Class
|
|
{
|
|
// Square wave class
|
|
public:
|
|
uint8_t NextByte();
|
|
void Init(int8_t Note);
|
|
|
|
};
|
|
|
|
class XT_SineWave_Class : public XT_Wave_Class
|
|
{
|
|
// Square wave class
|
|
private:
|
|
float AngleIncrement; // The amount the angle changes per sample
|
|
float CurrentAngle; // The angle of the current sample
|
|
public:
|
|
uint8_t NextByte();
|
|
void Init(int8_t Note);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Instrument class, main class for producing sounds, from simple tones to more complex sounds
|
|
// on it's own it will only produce simple tones, but in combination with an
|
|
// envelope object it can be made to simulate more complex audio/ instruments
|
|
class XT_Instrument_Class : public XT_PlayListItem_Class
|
|
{
|
|
// max frequency depends on the type of wave being produced :
|
|
// Square wave : 25Khz
|
|
// Triangle :
|
|
// Sawtooth :
|
|
// Sine :
|
|
// If you exceed the max then the max freq. will be used, no error returned
|
|
|
|
private:
|
|
uint8_t CurrentByte; // value currently being sent to DAC
|
|
bool SoundCompleted; // true when sound has completed - not the
|
|
uint32_t SoundDurationCounter; // count down to 0 then stops sound,
|
|
uint32_t DurationCounter; // count down to 0 then marks as completed
|
|
void ClearEnvelopes();
|
|
|
|
// functions that set up instruments, called in the "SetInstrument" function below
|
|
// must be private to stop calling directly and forgetting to clear any envelopes
|
|
void SetDefaultInstrument();
|
|
void SetPianoInstrument();
|
|
void SetHarpsichordInstrument();
|
|
void SetOrganInstrument();
|
|
void SetSaxophoneInstrument();
|
|
|
|
public:
|
|
int8_t Note=-1; // as listed in MusicDefinitions.h
|
|
uint32_t SoundDuration; // Sound Duration, how long sound is heard
|
|
uint32_t Duration; // Duration before marked as completed
|
|
uint16_t Frequency=0; // only used if you set an exact frequency, leave as 0 to use note
|
|
XT_Envelope_Class* FirstEnvelope=0; // First envelope associated with this Instrument
|
|
XT_Envelope_Class *CurrentEnvelope=0; // Current envelope in force
|
|
XT_Wave_Class* WaveForm=0; // i.e. square, sawtooth, sine, triangle
|
|
|
|
// Constructors
|
|
XT_Instrument_Class();
|
|
XT_Instrument_Class(int16_t InstrumentID);
|
|
XT_Instrument_Class(int16_t InstrumentID, uint8_t Volume);
|
|
|
|
// functions
|
|
uint8_t NextByte() override;
|
|
void Init() override; // initialize any default values
|
|
void SetNote(int8_t Note);
|
|
void SetFrequency(uint16_t Freq); // only if not using note property
|
|
void SetDuration(uint32_t Length); // in millis
|
|
void SetWaveForm(uint8_t WaveFormType);
|
|
void SetInstrument(uint16_t Instrument);
|
|
XT_Envelope_Class *AddEnvelope();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Music Score class
|
|
|
|
class XT_MusicScore_Class:public XT_PlayListItem_Class
|
|
{
|
|
// a class that handles basic musical scores (sheet music) to allow the play back of a single
|
|
// musical score for 1 instrument, at present cannot handle chords
|
|
|
|
private:
|
|
uint32_t ChangeNoteEvery; // how often to change to a new note
|
|
// in 50,000's of a second (the internal
|
|
// sample rate sent to the DAC
|
|
// calculated from the tempo value below
|
|
uint32_t ChangeNoteCounter; // countdown counter for the above
|
|
uint16_t ScoreIdx; // Index position of next note to play
|
|
public:
|
|
XT_Instrument_Class* Instrument; // The instrument to use
|
|
uint16_t Tempo; // In Beats Per Min (as used in music)
|
|
|
|
int8_t* Score=0; // the score itself, consists of the note
|
|
// and then optionally an alteration to the
|
|
// default duration. By default a note will
|
|
// play for 1 beat, if you pass a beat after
|
|
// note then it's the beat in 1/4 beats, i.e.
|
|
// 1 = 0.25 beat,2=0.5 beat, 3=0.75, 4=1 beat
|
|
// (the default) 5=1.25 beats etc. up to
|
|
// 126 (31.5 beats)
|
|
// The note should be passed as a negative value
|
|
// of the real index into the frequency array
|
|
// you do not need to worry about this if you
|
|
// use the preset defines in the Pitches.h
|
|
// file
|
|
// a value of -127 must be put at the end
|
|
// of the data to signify the end. Again use
|
|
// defines in Pitches.h
|
|
|
|
|
|
// constructors
|
|
XT_MusicScore_Class(int8_t* Score);
|
|
XT_MusicScore_Class(int8_t* Score,uint16_t Tempo);
|
|
XT_MusicScore_Class(int8_t* Score,uint16_t Tempo,XT_Instrument_Class* Instrument);
|
|
XT_MusicScore_Class(int8_t* Score,uint16_t Tempo,uint16_t InstrumentID);
|
|
|
|
// override functions
|
|
uint8_t NextByte()override;
|
|
void Init()override;
|
|
void SetInstrument(uint16_t InstrumentID);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Sequence definitions
|
|
|
|
class XT_SequenceItem_Class
|
|
{
|
|
// An item that contains information about what to play and links to the next item
|
|
// in the list of things to play
|
|
public:
|
|
XT_PlayListItem_Class *PlayItem; // The item to play
|
|
XT_SequenceItem_Class *NextItem=0; // Next item to play
|
|
};
|
|
|
|
|
|
class XT_Sequence_Class:public XT_PlayListItem_Class
|
|
{
|
|
// allows you to queue up many playlist items one after the other with minimum effort
|
|
|
|
private:
|
|
XT_SequenceItem_Class *CurrentItem=0; // current item playing from within the list
|
|
XT_SequenceItem_Class *FirstItem=0; // first play list item to play in linked list
|
|
XT_SequenceItem_Class *LastItem=0; // last play list item to play in linked list
|
|
// the class itself is a playlist item
|
|
public:
|
|
uint8_t NextByte();
|
|
void Init();
|
|
void AddPlayItem(XT_PlayListItem_Class *PlayItem);
|
|
void RemoveAllPlayItems(); // remove all play items
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// the main class for using the DAC to play sounds
|
|
class XT_DAC_Audio_Class
|
|
{
|
|
|
|
public:
|
|
|
|
XT_DAC_Audio_Class(uint8_t TheDacPin, uint8_t TimerNo);
|
|
XT_DAC_Audio_Class(uint8_t TheDacPin, uint8_t TimerNo,uint16_t PassedBufferSize);
|
|
|
|
void FillBuffer(); // Fills the buffer of values to send to the DAC, put in your
|
|
// main loop, see BUFFER_SIZE comments at top of code.
|
|
uint8_t MixBytesToPlay();
|
|
void Play(XT_PlayListItem_Class *Sound);
|
|
void Play(XT_PlayListItem_Class *Sound,bool Mix);
|
|
void StopAllSounds();
|
|
bool AlreadyPlaying(XT_PlayListItem_Class *Item);
|
|
void RemoveFromPlayList(XT_PlayListItem_Class *ItemToRemove);
|
|
int BufferUsage();
|
|
|
|
// debug
|
|
void PrintPlayList();
|
|
|
|
|
|
};
|
|
|