diff --git a/AudioOutput.h b/AudioOutput.h index fa5b8e889..46581d098 100644 --- a/AudioOutput.h +++ b/AudioOutput.h @@ -57,6 +57,7 @@ #ifndef AUDIOOUTPUT_H #define AUDIOOUTPUT_H +#include /** The type used to store a single channel of a single frame, internally. For compatibility with earlier versions of Mozzi this is defined as int. * If you do not care about keeping old sketches working, you may be able to save some RAM by using int16_t, instead (on boards where int is larger @@ -134,8 +135,11 @@ struct MonoOutput { /** Construct an audio frame from a zero-centered value known to be in the 8 bit range. On AVR, if MOZZI_OUTPUT_PWM mode, this is effectively the same as calling the * constructor, directly (no scaling gets applied). On platforms/configs using more bits, an appropriate left-shift will be performed. */ static inline MonoOutput from8Bit(int16_t l) { return fromNBit(8, l); } - /** Construct an audio frame a zero-centered value known to be in the 16 bit range. This is jsut a shortcut for fromNBit(16, ...) provided for convenience. */ + /** Construct an audio frame from a zero-centered value known to be in the 16 bit range. This is jsut a shortcut for fromNBit(16, ...) provided for convenience. */ static inline MonoOutput from16Bit(int16_t l) { return fromNBit(16, l); } + /** Construct an audio frame from a SFix type from FixMath. Mozzi will figure out how many bits are in there and performs appropriate shifting to match the output range. */ + template + static inline MonoOutput fromSFix(SFix l) { return MonoOutput(SCALE_AUDIO(l.asRaw(), (NI+NF+1))) ;} /** Construct an audio frame a zero-centered value known to be above at almost but not quite the N bit range, e.g. at N=8 bits and a litte. On most platforms, this is * exactly the same as fromNBit(), shifting up or down to the platforms' available resolution. * @@ -179,6 +183,9 @@ template static inline StereoOutput fromNBit(uint8_t bits, T l, T r) static inline StereoOutput from8Bit(int16_t l, int16_t r) { return fromNBit(8, l, r); } /** See @ref MonoOutput::from16Bit(), stereo variant */ static inline StereoOutput from16Bit(int16_t l, int16_t r) { return fromNBit(16, l, r); } +/** See @ref MonoOutput::fromSFix(), stereo variant. Note that the two channels do not need to have the same number of bits. */ + template + static inline StereoOutput fromSFix(SFix l, SFix<_NI,_NF,_RANGE> r) { return StereoOutput(SCALE_AUDIO(l.asRaw(), (NI+NF+1)), SCALE_AUDIO(r.asRaw(), (_NI+_NF+1))); } /** See @ref MonoOutput::fromAlmostNBit(), stereo variant */ template static inline StereoOutput fromAlmostNBit(A bits, B l, B r) { return StereoOutput(SCALE_AUDIO_NEAR(l, bits), SCALE_AUDIO_NEAR(r, bits)); } private: diff --git a/Line.h b/Line.h index 203293239..fabdfbb27 100644 --- a/Line.h +++ b/Line.h @@ -15,6 +15,8 @@ #include +#include + /** For linear changes with a minimum of calculation at each step. For instance, you can use Line to make an oscillator glide from one frequency to another, pre-calculating the required phase increments for each end and then letting your @@ -30,12 +32,15 @@ represent fractional numbers. Google "fixed point arithmetic" if this is new to you. */ + + + template class Line { private: - volatile T current_value; // volatile because it could be set in control interrupt and updated in audio - volatile T step_size; + T current_value; + T step_size; public: /** Constructor. Use the template parameter to set the type of numbers you @@ -109,7 +114,7 @@ template <> class Line { private: - volatile unsigned char current_value; // volatile because it could be set in control interrupt and updated in audio + unsigned char current_value; char step_size; public: @@ -178,7 +183,7 @@ template <> class Line { private: - volatile unsigned int current_value; // volatile because it could be set in control interrupt and updated in audio + unsigned int current_value; int step_size; public: @@ -250,7 +255,7 @@ template <> class Line { private: - volatile unsigned long current_value; // volatile because it could be set in control interrupt and updated in audio + unsigned long current_value; long step_size; public: @@ -312,6 +317,174 @@ class Line } }; + +/* UFix specialisation */ +template +class Line> +{ +private: + typedef UFix internal_type; + internal_type current_value; + SFix step_size; + +public: + /** Constructor. Use the template parameter to set the type of numbers you + want to use. For example, Line \ myline; makes a Line which uses ints. + */ + Line (){;} + + /** Increments one step along the line. + @return the next value. + */ + inline + internal_type next() + { + current_value = current_value + step_size; + return current_value; + } + + /** Set the current value of the line. + The Line will continue incrementing from this + value using any previously calculated step size. + @param value the number to set the Line's current_value to. + */ + inline + void set(internal_type value) + { + current_value=value; + } + + /** Given a target value and the number of steps to take on the way, this calculates the step size needed to get there from the current value. + @param targetvalue the value to move towards. + @param num_steps how many steps to take to reach the target as a UFix<_NI,0> + */ + template + void set(internal_type targetvalue, UFix<_NI,0> num_steps) + { + if(num_steps.asRaw()) { + auto numerator = targetvalue-current_value; + step_size = numerator*num_steps.invAccurate(); + } else { + step_size = 0; + current_value = targetvalue; + } + } + + + /** Given a target value and the number of steps to take on the way, this calculates the step size needed to get there from the current value. + @param targetvalue the value to move towards. + @param num_steps how many steps to take to reach the target. + */ + template + void set(internal_type targetvalue, T num_steps) + { + if(num_steps) { + auto numerator = targetvalue-current_value; + step_size = internal_type(numerator.asRaw()/num_steps,true); + } else { + step_size = 0; + current_value = targetvalue; + } + } + + /** Given a new starting value, target value and the number of steps to take on the way, this sets the step size needed to get there. + @param startvalue the number to set the Line's current_value to. + @param targetvalue the value to move towards. + @param num_steps how many steps to take to reach the target. + */ + template + void set(internal_type startvalue, internal_type targetvalue, T num_steps) + { + set(startvalue); + set(targetvalue, num_steps); + } +}; + + +/* SFix specialisation (if someone has an idea to avoid duplication with UFix) */ +template +class Line> +{ +private: + typedef SFix internal_type; + internal_type current_value; + SFix step_size; + +public: + /** Constructor. Use the template parameter to set the type of numbers you + want to use. For example, Line \ myline; makes a Line which uses ints. + */ + Line (){;} + + /** Increments one step along the line. + @return the next value. + */ + inline + internal_type next() + { + current_value = current_value + step_size; + return current_value; + } + + /** Set the current value of the line. + The Line will continue incrementing from this + value using any previously calculated step size. + @param value the number to set the Line's current_value to. + */ + inline + void set(internal_type value) + { + current_value=value; + } + + /** Given a target value and the number of steps to take on the way, this calculates the step size needed to get there from the current value. + @param targetvalue the value to move towards. + @param num_steps how many steps to take to reach the target as a UFix<_NI,0> + */ + template + void set(internal_type targetvalue, UFix<_NI,0> num_steps) + { + if(num_steps.asRaw()) { + auto numerator = targetvalue-current_value; + step_size = numerator*num_steps.invAccurate(); + } else { + step_size = 0; + current_value = targetvalue; + } + } + + + /** Given a target value and the number of steps to take on the way, this calculates the step size needed to get there from the current value. + @param targetvalue the value to move towards. + @param num_steps how many steps to take to reach the target. + */ + template + void set(internal_type targetvalue, T num_steps) + { + if(num_steps) { + auto numerator = targetvalue-current_value; + step_size = internal_type(numerator.asRaw()/num_steps,true); + } else { + step_size = 0; + current_value = targetvalue; + } + } + + /** Given a new starting value, target value and the number of steps to take on the way, this sets the step size needed to get there. + @param startvalue the number to set the Line's current_value to. + @param targetvalue the value to move towards. + @param num_steps how many steps to take to reach the target. + */ + template + void set(internal_type startvalue, internal_type targetvalue, T num_steps) + { + set(startvalue); + set(targetvalue, num_steps); + } +}; + + + /** @example 02.Control/Control_Tremelo/Control_Tremelo.ino This example demonstrates the Line class. diff --git a/Oscil.h b/Oscil.h index 531bf190a..b5253a9b1 100644 --- a/Oscil.h +++ b/Oscil.h @@ -159,9 +159,9 @@ class Oscil each direction. This fixed point math number is interpreted as a SFix<15,16> internally. @return a sample from the table. */ - template + template inline - int8_t phMod(SFix phmod_proportion) + int8_t phMod(SFix phmod_proportion) { return phMod(SFix<15,16>(phmod_proportion).asRaw()); } @@ -216,11 +216,11 @@ class Oscil @note This didn't run faster than float last time it was tested, after 2014 code changes. Need to see if 2014 changes improved or worsened performance. @param frequency in UFix fixed-point number format. */ - template + template inline - void setFreq(UFix frequency) + void setFreq(UFix frequency) { - setFreq_Q16n16(UFix<16,16>(frequency).asRaw()); + setFreq_Q16n16(UFix<16,16>(frequency).asRaw()); } @@ -256,8 +256,9 @@ class Oscil less than 64 Hz. @param frequency in UFix<24,8> fixed-point number format. */ + template inline - void setFreq(UFix<24,8> frequency) + void setFreq(UFix<24,8,RANGE> frequency) { setFreq_Q24n8(frequency.asRaw()); } @@ -295,8 +296,9 @@ class Oscil @note This didn't run faster than float last time it was tested, after 2014 code changes. Need to see if 2014 changes improved or worsened performance. @param frequency in UFix<16,16> fixed-point number format. */ + template inline - void setFreq(UFix<16,16> frequency) + void setFreq(UFix<16,16,RANGE> frequency) { setFreq_Q16n16(frequency.asRaw()); } @@ -309,9 +311,9 @@ class Oscil @note This didn't run faster than float last time it was tested, after 2014 code changes. Need to see if 2014 changes improved or worsened performance. @param frequency in SFix<16,16> fixed-point number format. */ - template + template inline - void setFreq(SFix frequency) + void setFreq(SFix frequency) { setFreq_Q16n16(UFix<16,16>(frequency).asRaw()); } diff --git a/Smooth.h b/Smooth.h index 3802ca6f8..34d22a459 100644 --- a/Smooth.h +++ b/Smooth.h @@ -268,6 +268,160 @@ class Smooth /** @endcond */ + +/* Specialization for UFix */ +template +class Smooth> +{ +private: + typedef UFix internal_type; + internal_type last_out; + UFix<0,16> a; + +public: + + + /** Constructor. + @param smoothness sets how much smoothing the filter will apply to + its input. Use a float or a UFix<0,NF> in the range 0~1, where 0 is not very smooth and 0.99 is + very smooth. + */ + template + Smooth(T smoothness) + { + setSmoothness(smoothness); + } + + /** Constructor. + This constructor which doesn't take a smoothness parameter is useful when you incorporate Smooth into another class definition. + You need to call setSmoothness(float) for your object before using Smooth. + @note there's probably a better way to do this... + */ + Smooth() + {} + + + /** Filters the input and returns the filtered value. You can also use the operator() function, eg. something like mySmoother(input-value). + @param in the signal to be smoothed. + @return the filtered signal. + */ + inline + internal_type next(internal_type in) + { + internal_type out = last_out + a * (in - last_out); // With FixMath, the syntax is actually the same than with floats :) + last_out = out; + return out; + } + + + + inline + internal_type operator()(internal_type n) { + return next(n); + } + + /** Sets how much smoothing the filter will apply to its input. + @param smoothness sets how much smoothing the filter will apply to + its input. Use a float in the range 0~1, where 0 is not very smooth and 0.99 is + very smooth. + */ + inline + void setSmoothness(float smoothness) + { + a=internal_type(1.f-smoothness); + } + + /** Sets how much smoothing the filter will apply to its input. + @param smoothness sets how much smoothing the filter will apply to + its input. Use a UFix<0,NF> in the range 0~1, where 0 is not very smooth and 0.99 is + very smooth. + */ + template + void setSmoothness(UFix<0,_NF> smoothness) + { + a = UFix<1,0>(1) - smoothness; + } + +}; + + + + +/* Specialization for SFix */ +template +class Smooth> +{ +private: + typedef SFix internal_type; + internal_type last_out; + UFix<0,16> a; + +public: + + + /** Constructor. + @param smoothness sets how much smoothing the filter will apply to + its input. Use a float or a UFix<0,NF> in the range 0~1, where 0 is not very smooth and 0.99 is + very smooth. + */ + template + Smooth(T smoothness) + { + setSmoothness(smoothness); + } + + /** Constructor. + This constructor which doesn't take a smoothness parameter is useful when you incorporate Smooth into another class definition. + You need to call setSmoothness(float) for your object before using Smooth. + @note there's probably a better way to do this... + */ + Smooth() + {} + + + /** Filters the input and returns the filtered value. You can also use the operator() function, eg. something like mySmoother(input-value). + @param in the signal to be smoothed. + @return the filtered signal. + */ + inline + internal_type next(internal_type in) + { + internal_type out = last_out + a * (in - last_out); + last_out = out; + return out; + } + + + + inline + internal_type operator()(internal_type n) { + return next(n); + } + + /** Sets how much smoothing the filter will apply to its input. + @param smoothness sets how much smoothing the filter will apply to + its input. Use a float in the range 0~1, where 0 is not very smooth and 0.99 is + very smooth. + */ + inline + void setSmoothness(float smoothness) + { + a=internal_type(1.f-smoothness); + } + + /** Sets how much smoothing the filter will apply to its input. + @param smoothness sets how much smoothing the filter will apply to + its input. Use a UFix<0,NF> in the range 0~1, where 0 is not very smooth and 0.99 is + very smooth. + */ + template + void setSmoothness(UFix<0,_NF> smoothness) + { + a = UFix<1,0>(1) - smoothness; + } + +}; + /** @example 05.Control_Filters/Smooth/Smooth.ino This example demonstrates the Smooth class. diff --git a/examples/05.Control_Filters/Line_vs_Smooth/Line_vs_Smooth.ino b/examples/05.Control_Filters/Line_vs_Smooth/Line_vs_Smooth.ino index cd50799ba..6068a2ca1 100644 --- a/examples/05.Control_Filters/Line_vs_Smooth/Line_vs_Smooth.ino +++ b/examples/05.Control_Filters/Line_vs_Smooth/Line_vs_Smooth.ino @@ -6,7 +6,7 @@ updated in the background while audio generation continues, and the most recent readings can be read anytime from an array. Also demonstrates linear interpolation with Line(), - filtering with Smooth(), and fixed point numbers. + filtering with Smooth(), and fixed point numbers from FixMath Circuit: Audio output on digital pin 9 (for standard output on a Uno or similar), or @@ -22,6 +22,7 @@ Mozzi help/discussion/announcements: https://groups.google.com/forum/#!forum/mozzi-users + Copyright 2013-2024 Tim Barrass and the Mozzi Team Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later. @@ -31,7 +32,7 @@ #include #include #include // sine table for oscillator -#include +#include #include #include #include @@ -42,13 +43,13 @@ Oscil aSin1(SIN2048_DATA); // Line to interpolate frequency for aSin0. -// Q16n16 is basically (yourNumber << 16) +// UFix<16,16>(yourNumber) is basically encoded in 16bits +// with 16 extra bits for additionnal precision. // Line needs the small analog input integer values of 0-1023 // to be scaled up if the time (the number of steps) is big // enough that distance/time gives a step-size of 0. -// Then you need floats (which are sometimes too slow and create glitches), -// or fixed point. Q16n16: ugly but good. -Line aInterpolate; +// Then you need floats (which are sometimes too slow and create glitches). +Line > aInterpolate; // the number of audio steps the line has to take to reach the next control value const unsigned int AUDIO_STEPS_PER_CONTROL = MOZZI_AUDIO_RATE / MOZZI_CONTROL_RATE; @@ -62,29 +63,31 @@ Smooth aSmooth(smoothness); // to smooth frequency for aSin1 void setup(){ - aSin0.setFreq(660.f); - aSin1.setFreq(220.f); + aSin0.setFreq(660); + aSin1.setFreq(220); startMozzi(); } -volatile unsigned int freq1; // global so it can be used in updateAudio, volatile to stop it getting changed while being used +unsigned int freq1; // global so it can be used in updateAudio void updateControl(){ - Q16n16 freq0 = Q16n0_to_Q16n16(mozziAnalogRead<10>(0)); // 0 to 1023, scaled up to Q16n16 format - freq1 = (unsigned int) mozziAnalogRead<10>(1); // 0 to 1023 + UFix<16,16> freq0 = mozziAnalogRead(0); // 0 to 1023, with an additionnal 16bits of precision (which will be used in the interpolation.) + freq1 = (unsigned int) mozziAnalogRead(1); // 0 to 1023 aInterpolate.set(freq0, AUDIO_STEPS_PER_CONTROL); } AudioOutput updateAudio(){ - Q16n16 interpolatedFreq = aInterpolate.next(); // get the next linear interpolated freq - aSin0.setFreq_Q16n16(interpolatedFreq); + auto interpolatedFreq = aInterpolate.next(); // get the next linear interpolated freq + aSin0.setFreq(interpolatedFreq); int smoothedFreq = aSmooth.next(freq1); // get the next filtered frequency aSin1.setFreq(smoothedFreq); - return MonoOutput::fromNBit(9, (int) (aSin0.next() + aSin1.next())); + + // Here we add to SFix numbers, created from the Oscil, for the output. Mozzi knows what is the final range of this allowing for auto-scaling. + return MonoOutput::fromSFix(toSFraction(aSin0.next()) + toSFraction(aSin1.next())); // auto-scaling of the output. } diff --git a/examples/06.Synthesis/AMsynth/AMsynth.ino b/examples/06.Synthesis/AMsynth/AMsynth.ino index 0c1a5ed17..312e0fe7a 100644 --- a/examples/06.Synthesis/AMsynth/AMsynth.ino +++ b/examples/06.Synthesis/AMsynth/AMsynth.ino @@ -22,14 +22,16 @@ Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later. */ -#define MOZZI_CONTROL_RATE 64 // Hz, powers of 2 are most reliable + +#define MOZZI_CONTROL_RATE 64 // Hz, powers of 2 are most reliable #include #include -#include // table for Oscils to play -#include +#include // table for Oscils to play #include #include #include +#include + // audio oscils Oscil aCarrier(COS2048_DATA); @@ -37,76 +39,71 @@ Oscil aModulator(COS2048_DATA); Oscil aModDepth(COS2048_DATA); // for scheduling note changes in updateControl() -EventDelay kNoteChangeDelay; +EventDelay kNoteChangeDelay; -// synthesis parameters in fixed point formats -Q8n8 ratio; // unsigned int with 8 integer bits and 8 fractional bits -Q24n8 carrier_freq; // unsigned long with 24 integer bits and 8 fractional bits -Q24n8 mod_freq; // unsigned long with 24 integer bits and 8 fractional bits +UFix<8, 8> ratio; // unsigned int with 8 integer bits and 8 fractional bits +UFix<24, 8> carrier_freq; // unsigned long with 24 integer bits and 8 fractional bits // for random notes -Q8n0 octave_start_note = 42; +const UFix<7, 0> octave_start_note = 42; -void setup(){ - ratio = float_to_Q8n8(3.0f); // define modulation ratio in float and convert to fixed-point - kNoteChangeDelay.set(200); // note duration ms, within resolution of MOZZI_CONTROL_RATE - aModDepth.setFreq(13.f); // vary mod depth to highlight am effects - randSeed(); // reseed the random generator for different results each time the sketch runs +void setup() { + ratio = 3; + kNoteChangeDelay.set(200); // note duration ms, within resolution of MOZZI_CONTROL_RATE + aModDepth.setFreq(13.f); // vary mod depth to highlight am effects + randSeed(); // reseed the random generator for different results each time the sketch runs startMozzi(); } -void updateControl(){ - static Q16n16 last_note = octave_start_note; - if(kNoteChangeDelay.ready()){ +void updateControl() { + static auto last_note = octave_start_note; + + if (kNoteChangeDelay.ready()) { // change octave now and then - if(rand((byte)5)==0){ - last_note = 36+(rand((byte)6)*12); + if (rand((byte)5) == 0) { + last_note = UFix<7, 0>(36 + (rand((byte)6) * 12)); } // change step up or down a semitone occasionally - if(rand((byte)13)==0){ - last_note += 1-rand((byte)3); + if (rand((byte)13) == 0) { + last_note = last_note + SFix<7, 0>(1 - rand((byte)3)); } // change modulation ratio now and then - if(rand((byte)5)==0){ - ratio = ((Q8n8) 1+ rand((byte)5)) <<8; + if (rand((byte)5) == 0) { + ratio = 1 + rand((byte)5); } // sometimes add a fractionto the ratio - if(rand((byte)5)==0){ - ratio += rand((byte)255); + if (rand((byte)5) == 0) { + ratio = ratio + toUFraction(rand((byte)255)); } // step up or down 3 semitones (or 0) - last_note += 3 * (1-rand((byte)3)); + last_note = last_note + SFix<7, 0>(3 * (1 - rand((byte)3))); // convert midi to frequency - Q16n16 midi_note = Q8n0_to_Q16n16(last_note); - carrier_freq = Q16n16_to_Q24n8(Q16n16_mtof(midi_note)); + carrier_freq = mtof(last_note); // calculate modulation frequency to stay in ratio with carrier - mod_freq = (carrier_freq * ratio)>>8; // (Q24n8 Q8n8) >> 8 = Q24n8 + auto mod_freq = carrier_freq * ratio; - // set frequencies of the oscillators - aCarrier.setFreq_Q24n8(carrier_freq); - aModulator.setFreq_Q24n8(mod_freq); + // set frequencies of the oscillators + aCarrier.setFreq(carrier_freq); + aModulator.setFreq(mod_freq); // reset the note scheduler kNoteChangeDelay.start(); } - - } -AudioOutput updateAudio(){ - long mod = (128u+ aModulator.next()) * ((byte)128+ aModDepth.next()); - return MonoOutput::fromNBit(24, mod * aCarrier.next()); +AudioOutput updateAudio() { + auto mod = UFix<8, 0>(128 + aModulator.next()) * UFix<8, 0>(128 + aModDepth.next()); + return MonoOutput::fromSFix(mod * toSFraction(aCarrier.next())); } - -void loop(){ +void loop() { audioHook(); } diff --git a/examples/06.Synthesis/AMsynth_HIFI/AMsynth_HIFI.ino b/examples/06.Synthesis/AMsynth_HIFI/AMsynth_HIFI.ino index 953c05294..cfe01f8cc 100644 --- a/examples/06.Synthesis/AMsynth_HIFI/AMsynth_HIFI.ino +++ b/examples/06.Synthesis/AMsynth_HIFI/AMsynth_HIFI.ino @@ -7,7 +7,7 @@ values, random numbers with rand(), and EventDelay() for scheduling. - Important: + Important: This sketch uses MOZZI_OUTPUT_2PIN_PWM (aka HIFI) output mode, which is not available on all boards (among others, it works on the classic Arduino boards, but not Teensy 3.x and friends). @@ -41,16 +41,17 @@ Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later. */ + #include #define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM - #include #include -#include // table for Oscils to play -#include +#include // table for Oscils to play #include #include #include +#include + // audio oscils Oscil aCarrier(COS2048_DATA); @@ -58,76 +59,71 @@ Oscil aModulator(COS2048_DATA); Oscil aModDepth(COS2048_DATA); // for scheduling note changes in updateControl() -EventDelay kNoteChangeDelay; +EventDelay kNoteChangeDelay; -// synthesis parameters in fixed point formats -Q8n8 ratio; // unsigned int with 8 integer bits and 8 fractional bits -Q24n8 carrier_freq; // unsigned long with 24 integer bits and 8 fractional bits -Q24n8 mod_freq; // unsigned long with 24 integer bits and 8 fractional bits +UFix<8, 8> ratio; // unsigned int with 8 integer bits and 8 fractional bits +UFix<24, 8> carrier_freq; // unsigned long with 24 integer bits and 8 fractional bits // for random notes -Q8n0 octave_start_note = 42; - -void setup(){ - ratio = float_to_Q8n8(3.0f); // define modulation ratio in float and convert to fixed-point - kNoteChangeDelay.set(200); // note duration ms, within resolution of MOZZI_CONTROL_RATE - aModDepth.setFreq(13.f); // vary mod depth to highlight am effects - randSeed(); // reseed the random generator for different results each time the sketch runs - startMozzi(); // use default MOZZI_CONTROL_RATE 64 +const UFix<7, 0> octave_start_note = 42; + +void setup() { + ratio = 3; + kNoteChangeDelay.set(200); // note duration ms, within resolution of MOZZI_CONTROL_RATE + aModDepth.setFreq(13.f); // vary mod depth to highlight am effects + randSeed(); // reseed the random generator for different results each time the sketch runs + startMozzi(); } -void updateControl(){ - static Q16n16 last_note = octave_start_note; +void updateControl() { + static auto last_note = octave_start_note; - if(kNoteChangeDelay.ready()){ + if (kNoteChangeDelay.ready()) { // change octave now and then - if(rand((byte)5)==0){ - last_note = 36+(rand((byte)6)*12); + if (rand((byte)5) == 0) { + last_note = UFix<7, 0>(36 + (rand((byte)6) * 12)); } // change step up or down a semitone occasionally - if(rand((byte)13)==0){ - last_note += 1-rand((byte)3); + if (rand((byte)13) == 0) { + last_note = last_note + SFix<7, 0>(1 - rand((byte)3)); } // change modulation ratio now and then - if(rand((byte)5)==0){ - ratio = ((Q8n8) 1+ rand((byte)5)) <<8; + if (rand((byte)5) == 0) { + ratio = 1 + rand((byte)5); } - // sometimes add a fraction to the ratio - if(rand((byte)5)==0){ - ratio += rand((byte)255); + // sometimes add a fractionto the ratio + if (rand((byte)5) == 0) { + ratio = ratio + toUFraction(rand((byte)255)); } // step up or down 3 semitones (or 0) - last_note += 3 * (1-rand((byte)3)); + last_note = last_note + SFix<7, 0>(3 * (1 - rand((byte)3))); // convert midi to frequency - Q16n16 midi_note = Q8n0_to_Q16n16(last_note); - carrier_freq = Q16n16_to_Q24n8(Q16n16_mtof(midi_note)); + carrier_freq = mtof(last_note); // calculate modulation frequency to stay in ratio with carrier - mod_freq = (carrier_freq * ratio)>>8; // (Q24n8 Q8n8) >> 8 = Q24n8 + auto mod_freq = carrier_freq * ratio; // set frequencies of the oscillators - aCarrier.setFreq_Q24n8(carrier_freq); - aModulator.setFreq_Q24n8(mod_freq); + aCarrier.setFreq(carrier_freq); + aModulator.setFreq(mod_freq); // reset the note scheduler kNoteChangeDelay.start(); } } - -AudioOutput updateAudio(){ - unsigned int mod = (128u+ aModulator.next()) * ((byte)128+ aModDepth.next()); - return MonoOutput::fromNBit(24, (long)mod * aCarrier.next()); // 16 bit * 8 bit = 24 bit +AudioOutput updateAudio() { + auto mod = UFix<8, 0>(128 + aModulator.next()) * UFix<8, 0>(128 + aModDepth.next()); + return MonoOutput::fromSFix(mod * toSFraction(aCarrier.next())); } - -void loop(){ +void loop() { audioHook(); } diff --git a/examples/06.Synthesis/Detuned_Beats_Wash/Detuned_Beats_Wash.ino b/examples/06.Synthesis/Detuned_Beats_Wash/Detuned_Beats_Wash.ino index b54832c4b..e6fdfc1a8 100644 --- a/examples/06.Synthesis/Detuned_Beats_Wash/Detuned_Beats_Wash.ino +++ b/examples/06.Synthesis/Detuned_Beats_Wash/Detuned_Beats_Wash.ino @@ -6,12 +6,12 @@ updateControl(), and the outputs of 12 audio oscillators are summed in updateAudio(). - Demonstrates the use of fixed-point Q16n16 + Demonstrates the use of FixMath fixed point format numbers, mtof() for converting midi note values to frequency, and xorshift96() for random numbers. This sketch is pushing the limits of computing power on the - 8-biit AVR boards. At the time of this writing, you will have + 8-bit AVR boards. At the time of this writing, you will have to manually alter your platform.txt file to use optimization for speed rather than size on Arduino Uno and similar. (Alternatively, remove one of the oscillators) @@ -36,7 +36,7 @@ #include #include #include -#include // for Q16n16 fixed-point fractional number type +#include // harmonics Oscil aCos1(COS8192_DATA); @@ -57,13 +57,19 @@ Oscil aCos6b(COS8192_DATA); //Oscil aCos7b(COS8192_DATA); // base pitch frequencies in Q16n16 fixed int format (for speed later) -Q16n16 f1,f2,f3,f4,f5,f6;//,f7; +UFix<12,15> f1,f2,f3,f4,f5,f6;//,f7; -Q16n16 variation() { +/*Q16n16 variation() { // 32 random bits & with 524287 (b111 1111 1111 1111 1111) // gives between 0-8 in Q16n16 format return (Q16n16) (xorshift96() & 524287UL); +}*/ + +UFix<3,16> variation() // changing the return type here enables to easily + // increase or decrease the variation +{ + return UFix<3,16>::fromRaw(xorshift96() & 524287UL); } @@ -71,31 +77,31 @@ void setup(){ startMozzi(); // select base frequencies using mtof (midi to freq) and fixed-point numbers - f1 = Q16n16_mtof(Q16n0_to_Q16n16(48)); - f2 = Q16n16_mtof(Q16n0_to_Q16n16(74)); - f3 = Q16n16_mtof(Q16n0_to_Q16n16(64)); - f4 = Q16n16_mtof(Q16n0_to_Q16n16(77)); - f5 = Q16n16_mtof(Q16n0_to_Q16n16(67)); - f6 = Q16n16_mtof(Q16n0_to_Q16n16(57)); - // f7 = Q16n16_mtof(Q16n0_to_Q16n16(60)); + f1 = mtof(UFix<7,0>(48)); + f2 = mtof(UFix<7,0>(74)); + f3 = mtof(UFix<7,0>(64)); + f4 = mtof(UFix<7,0>(77)); + f5 = mtof(UFix<7,0>(67)); + f6 = mtof(UFix<7,0>(57)); + // f7 = mtof(UFix<7,0>(60)); // set Oscils with chosen frequencies - aCos1.setFreq_Q16n16(f1); - aCos2.setFreq_Q16n16(f2); - aCos3.setFreq_Q16n16(f3); - aCos4.setFreq_Q16n16(f4); - aCos5.setFreq_Q16n16(f5); - aCos6.setFreq_Q16n16(f6); - // aCos7.setFreq_Q16n16(f7); + aCos1.setFreq(f1); + aCos2.setFreq(f2); + aCos3.setFreq(f3); + aCos4.setFreq(f4); + aCos5.setFreq(f5); + aCos6.setFreq(f6); + // aCos7.setFreq(f7); // set frequencies of duplicate oscillators - aCos1b.setFreq_Q16n16(f1+variation()); - aCos2b.setFreq_Q16n16(f2+variation()); - aCos3b.setFreq_Q16n16(f3+variation()); - aCos4b.setFreq_Q16n16(f4+variation()); - aCos5b.setFreq_Q16n16(f5+variation()); - aCos6b.setFreq_Q16n16(f6+variation()); - //aCos7b.setFreq_Q16n16(f7+variation()); + aCos1b.setFreq(f1+variation()); + aCos2b.setFreq(f2+variation()); + aCos3b.setFreq(f3+variation()); + aCos4b.setFreq(f4+variation()); + aCos5b.setFreq(f5+variation()); + aCos6b.setFreq(f6+variation()); + //aCos7b.setFreq(f7+variation()); } @@ -112,31 +118,31 @@ void updateControl(){ switch (lowByte(xorshift96()) & 7){ // 7 is 0111 case 0: - aCos1b.setFreq_Q16n16(f1+variation()); + aCos1b.setFreq(f1+variation()); break; case 1: - aCos2b.setFreq_Q16n16(f2+variation()); + aCos2b.setFreq(f2+variation()); break; case 2: - aCos3b.setFreq_Q16n16(f3+variation()); + aCos3b.setFreq(f3+variation()); break; case 3: - aCos4b.setFreq_Q16n16(f4+variation()); + aCos4b.setFreq(f4+variation()); break; case 4: - aCos5b.setFreq_Q16n16(f5+variation()); + aCos5b.setFreq(f5+variation()); break; case 5: - aCos6b.setFreq_Q16n16(f6+variation()); + aCos6b.setFreq(f6+variation()); break; /* case 6: - aCos7b.setFreq_Q16n16(f7+variation()); + aCos7b.setFreq(f7+variation()); break; */ } @@ -144,8 +150,13 @@ void updateControl(){ AudioOutput updateAudio(){ + /* +The following block is the "classical" way of outputting the sound, from a standard C/C++ type. +You need to know how many bits you are dealing with and can use a reduced number to bring in some +distorsion with .clip() if you want. + */ - int asig = + /* int asig = aCos1.next() + aCos1b.next() + aCos2.next() + aCos2b.next() + aCos3.next() + aCos3b.next() + @@ -153,6 +164,26 @@ AudioOutput updateAudio(){ aCos5.next() + aCos5b.next() + aCos6.next() + aCos6b.next();// + // aCos7.next() + aCos7b.next(); + return MonoOutput::fromAlmostNBit(12, asig); +*/ - return MonoOutput::fromAlmostNBit(12, asig); + +/* + This is letting Mozzi compute the number of bits for you. + The syntax is a bit more cumbersome but FixMath will be + clever enough to figure out the exact number of bits needed + to create asig without any overflow, but no more. + This number of bits will be used by Mozzi for right/left shifting + the number to match the capability of the system. +*/ + auto asig = + toSFraction(aCos1.next()) + toSFraction(aCos1b.next()) + + toSFraction(aCos2.next()) + toSFraction(aCos2b.next()) + + toSFraction(aCos3.next()) + toSFraction(aCos3b.next()) + + toSFraction(aCos4.next()) + toSFraction(aCos4b.next()) + + toSFraction(aCos5.next()) + toSFraction(aCos5b.next()) + + toSFraction(aCos6.next()) + toSFraction(aCos6b.next()); /* + +toSFraction(aCos7.next()) + toSFraction(aCos7b.next()) +*/ + return MonoOutput::fromSFix(asig); + } diff --git a/examples/06.Synthesis/Difference_Tone/Difference_Tone.ino b/examples/06.Synthesis/Difference_Tone/Difference_Tone.ino index 9a7e22449..fdeb7bf19 100644 --- a/examples/06.Synthesis/Difference_Tone/Difference_Tone.ino +++ b/examples/06.Synthesis/Difference_Tone/Difference_Tone.ino @@ -3,6 +3,8 @@ using Mozzi sonification library. Demonstrates the use of EventDelay(), rand() and fixed-point numbers. + + Note that an example using the newer FixMath paradigm is also available: Difference_Tone_FixMath. Circuit: Audio output on digital pin 9 on a Uno or similar, or DAC/A14 on Teensy 3.1, or diff --git a/examples/06.Synthesis/Difference_Tone_FixMath/Difference_Tone_FixMath.ino b/examples/06.Synthesis/Difference_Tone_FixMath/Difference_Tone_FixMath.ino new file mode 100644 index 000000000..f9648964c --- /dev/null +++ b/examples/06.Synthesis/Difference_Tone_FixMath/Difference_Tone_FixMath.ino @@ -0,0 +1,71 @@ +/* Example using clipping to modify the spectrum of an audio signal + and emphasise a tone generated by the difference in frequency of 2 waves, + using Mozzi sonification library. + + Adaptation of the Difference_Tone example using FixMath. + + Demonstrates the use of EventDelay(), rand() and fixed-point numbers. + + Demonstrates Oscil::phMod() for phase modulation, + Smooth() for smoothing control signals, + and FixMath fixed point number types for fractional frequencies. + This is the same technique than the FMsynth example but + using FixMath instead of mozzi_fixmath. + + Mozzi documentation/API + https://sensorium.github.io/Mozzi/doc/html/index.html + + Mozzi help/discussion/announcements: + https://groups.google.com/forum/#!forum/mozzi-users + + Tim Barras, Thomas Combriat and the Mozzi team 2023, CC by-nc-sa. +*/ + + +#include +#include +#include +#include +#include +#include +#include + + +// use: Oscil oscilName (wavetable), look in .h file of table #included above +Oscil aSin1(SIN2048_DATA); // sine wave sound source +Oscil aSin2(SIN2048_DATA); // sine wave sound source +Oscil aGain(SIN2048_DATA); // to fade audio signal in and out before waveshaping + +// for scheduling note changes +EventDelay kChangeNoteDelay; + +const UFix<8, 0> freq1 = 184; +const auto harmonic_step = freq1 * UFix<8, 0>(12).invAccurate(); // harmonic_step = freq1/12; + +void setup() { + Serial.begin(115200); + aSin1.setFreq(freq1); + aGain.setFreq(0.2f); // use a float for low frequencies, in setup it doesn't need to be fast + kChangeNoteDelay.set(2000); // note duration ms, within resolution of CONTROL_RATE + startMozzi(); // :) +} + +void updateControl() { + if (kChangeNoteDelay.ready()) { + UFix<4, 0> harmonic = rand((byte)12); + auto shimmer = toUFraction(rand((byte)255)); // Creates a UFix<0,8> + auto freq2difference = (harmonic * harmonic_step) + (harmonic_step * shimmer).sR<3>(); // the shimmering is divided by 8 here + auto freq2 = (freq1 - freq2difference).asUFix(); + aSin2.setFreq((freq2)); + kChangeNoteDelay.start(); + } +} + +AudioOutput_t updateAudio() { + auto asig = (toSInt(aSin1.next()) + toSInt(aSin2.next())) * (toSFraction(aGain.next()) + UFix<1, 7>(1.2)); // this is a SFix<9,9> in the end + return MonoOutput::fromAlmostNBit(11, asig.asRaw()).clip(); // TODO, implement smart MonoOutput +} + +void loop() { + audioHook(); // required here +} diff --git a/examples/06.Synthesis/FMsynth_32k_HIFI/FMsynth_32k_HIFI.ino b/examples/06.Synthesis/FMsynth_32k_HIFI/FMsynth_32k_HIFI.ino index ef8f34034..dc765db74 100644 --- a/examples/06.Synthesis/FMsynth_32k_HIFI/FMsynth_32k_HIFI.ino +++ b/examples/06.Synthesis/FMsynth_32k_HIFI/FMsynth_32k_HIFI.ino @@ -3,7 +3,7 @@ Demonstrates Oscil::phMod() for phase modulation, Smooth() for smoothing control signals, - and Mozzi's fixed point number types for fractional frequencies. + and FixMath fixed point number types for fractional frequencies. This sketch using HIFI mode is not for Teensy 3.1. @@ -47,87 +47,76 @@ #include #define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM #define MOZZI_AUDIO_RATE 32768 -#define MOZZI_CONTROL_RATE 256 // Hz, powers of 2 are most reliable +#define MOZZI_CONTROL_RATE 256 // Hz, powers of 2 are most reliable #include #include -#include // table for Oscils to play +#include // table for Oscils to play #include -#include +#include #include #include + + Oscil aCarrier(COS2048_DATA); Oscil aModulator(COS2048_DATA); Oscil kModIndex(COS2048_DATA); -// The ratio of deviation to modulation frequency is called the "index of modulation". ( I = d / Fm ) -// It will vary according to the frequency that is modulating the carrier and the amount of deviation. -// so deviation d = I Fm -// haven't quite worked this out properly yet... +UFix<0, 16> mod_index; +UFix<8, 16> deviation; // 8 so that we do not exceed 32bits in updateAudio -Q8n8 mod_index;// = float_to_Q8n8(2.0f); // constant version -Q16n16 deviation; +UFix<16, 16> carrier_freq, mod_freq; +const UFix<0,16> modulation_amp = 0.001; // how much the modulation index will vary around its mean +const UFix<2,0> mean_modulation_unscaled = 2; // the real mean modulation will be mean_modulation_unscaled * modulation_max + // this one is adding a bias to the oscillator, hence it should be bigger than one. -Q16n16 carrier_freq, mod_freq; - -// FM ratio between oscillator frequencies, stays the same through note range -Q8n8 mod_to_carrier_ratio = float_to_Q8n8(3.f); +const UFix<2, 0> mod_to_carrier_ratio = 3; // 3 fits in UFix<2,0> which has a maximum range of (2^2)-1=3 EventDelay kNoteChangeDelay; -// for note changes -Q7n8 target_note, note0, note1, note_upper_limit, note_lower_limit, note_change_step, smoothed_note; - -// using Smooth on midi notes rather than frequency, -// because fractional frequencies need larger types than Smooth can handle -// Inefficient, but...until there is a better Smooth.... -Smooth kSmoothNote(0.95f); - -void setup(){ - kNoteChangeDelay.set(768); // ms countdown, taylored to resolution of MOZZI_CONTROL_RATE - kModIndex.setFreq(.768f); // sync with kNoteChangeDelay - target_note = note0; - note_change_step = Q7n0_to_Q7n8(3); - note_upper_limit = Q7n0_to_Q7n8(50); - note_lower_limit = Q7n0_to_Q7n8(32); - note0 = note_lower_limit; - note1 = note_lower_limit + Q7n0_to_Q7n8(5); +const UFix<7, 0> note_upper_limit = 50, note_lower_limit = 32; + +UFix<7, 0> note0 = note_lower_limit, note1 = note_lower_limit + UFix<7, 0>(5), target_note = 0; +SFix<2, 0> note_change_step = 3; // will only take +3 or -3, 2bits are enough for that + +UFix<7,8> smoothed_note; + +Smooth> kSmoothNote(0.95f); + +void setup() { + kNoteChangeDelay.set(768); + kModIndex.setFreq(.768f); // sync with kNoteChangeDelay startMozzi(); } -void setFreqs(Q8n8 midi_note){ - carrier_freq = Q16n16_mtof(Q8n8_to_Q16n16(midi_note)); // convert midi note to fractional frequency - mod_freq = ((carrier_freq>>8) * mod_to_carrier_ratio) ; // (Q16n16>>8) Q8n8 = Q16n16, beware of overflow - deviation = ((mod_freq>>16) * mod_index); // (Q16n16>>16) Q8n8 = Q24n8, beware of overflow - aCarrier.setFreq_Q16n16(carrier_freq); - aModulator.setFreq_Q16n16(mod_freq); +void setFreqs(UFix<7, 8> midi_note) { + carrier_freq = mtof(midi_note); + mod_freq = UFix<14,16>(carrier_freq) * mod_to_carrier_ratio; + deviation = ((UFix<16,0>(mod_freq)) * mod_index).sR<8>(); // the sR here cheaply divides the deviation by 256. + + aCarrier.setFreq(carrier_freq); + aModulator.setFreq(mod_freq); } -void updateControl(){ - // change note - if(kNoteChangeDelay.ready()){ - if (target_note==note0){ - note1 += note_change_step; - target_note=note1; - } - else{ - note0 += note_change_step; - target_note=note0; +void updateControl() { + if (kNoteChangeDelay.ready()) { + if (target_note == note0) + { + note1 = note1 + note_change_step; + target_note = note1; + } + else + { + note0 = note0 + note_change_step; + target_note = note0; } - - // change direction - if(note0>note_upper_limit) note_change_step = Q7n0_to_Q7n8(-3); - if(note0note_upper_limit) note_change_step = -3; + if(note0(); // the sR is to scale back in pure fractional range - // vary the modulation index - mod_index = (Q8n8)350+kModIndex.next(); - - // here's where the smoothing happens smoothed_note = kSmoothNote.next(target_note); setFreqs(smoothed_note); @@ -135,11 +124,11 @@ void updateControl(){ AudioOutput updateAudio(){ - Q15n16 modulation = deviation * aModulator.next() >> 8; - return MonoOutput::from8Bit(aCarrier.phMod(modulation)); // Internally still only 8 bits, will be shifted up to 14 bits in HIFI mode +auto modulation = (deviation * toSFraction(aModulator.next())); +return MonoOutput::from8Bit(aCarrier.phMod(modulation)); } -void loop(){ - audioHook(); -} +void loop() { + audioHook(); +} \ No newline at end of file diff --git a/examples/06.Synthesis/FMsynth_FixMath/FMsynth_FixMath.ino b/examples/06.Synthesis/FMsynth_FixMath/FMsynth_FixMath.ino new file mode 100644 index 000000000..0001c27ae --- /dev/null +++ b/examples/06.Synthesis/FMsynth_FixMath/FMsynth_FixMath.ino @@ -0,0 +1,108 @@ +/* Example of simple FM with the phase modulation technique, + using Mozzi sonification library. + + Demonstrates Oscil::phMod() for phase modulation, + Smooth() for smoothing control signals, + and FixMath fixed point number types for fractional frequencies. + This is the same technique than the FMsynth example but + using FixMath instead of mozzi_fixmath. + + Note that it is slightly less optimized (but runs fine on an + Uno) in order to make the underlying algorithm clearer. + An optimized version, using FixMath can be found in the + example FMsynth_32k_HIFI. + + Mozzi documentation/API + https://sensorium.github.io/Mozzi/doc/html/index.html + + Mozzi help/discussion/announcements: + https://groups.google.com/forum/#!forum/mozzi-users + + Tim Barrass 2012, Thomas Combriat and the Mozzi team 2023, CC by-nc-sa. +*/ + +#include +#define MOZZI_CONTROL_RATE 256 // Hz, powers of 2 are most reliable + +#include +#include +#include // table for Oscils to play +#include +#include +#include +#include + + + +Oscil aCarrier(COS2048_DATA); +Oscil aModulator(COS2048_DATA); +Oscil kModIndex(COS2048_DATA); + +UFix<0, 16> mod_index; +UFix<16, 16> deviation; + +UFix<16, 16> carrier_freq, mod_freq; +const UFix<0,16> modulation_amp = 0.001; // how much the modulation index will vary around its mean +const UFix<2,0> mean_modulation_unscaled = 2; // the real mean modulation will be mean_modulation_unscaled * modulation_max + // this one is adding a bias to the oscillator, hence it should be bigger than one. + +const UFix<2, 0> mod_to_carrier_ratio = 3; // 3 fits in UFix<2,0> which has a maximum range of (2^2)-1=3 + +EventDelay kNoteChangeDelay; + +const UFix<7, 0> note_upper_limit = 50, note_lower_limit = 32; + +UFix<7, 0> note0 = note_lower_limit, note1 = note_lower_limit + UFix<7, 0>(5), target_note = 0; +SFix<2, 0> note_change_step = 3; // will only take +3 or -3, 2bits are enough for that + +UFix<7,8> smoothed_note; + +Smooth> kSmoothNote(0.95f); + +void setup() { + kNoteChangeDelay.set(768); + kModIndex.setFreq(.768f); // sync with kNoteChangeDelay + startMozzi(); +} + +void setFreqs(UFix<7, 8> midi_note) { + carrier_freq = mtof(midi_note); + mod_freq = carrier_freq * mod_to_carrier_ratio; + deviation = mod_freq * mod_index; + aCarrier.setFreq(carrier_freq); + aModulator.setFreq(mod_freq); +} + +void updateControl() { + if (kNoteChangeDelay.ready()) { + if (target_note == note0) + { + note1 = note1 + note_change_step; + target_note = note1; + } + else + { + note0 = note0 + note_change_step; + target_note = note0; + } + if(note0>note_upper_limit) note_change_step = -3; + if(note0 #include #include +#include #include #include #include @@ -45,12 +46,11 @@ WaveShaper aCompress(WAVESHAPE_COMPRESS_512_TO_488_DATA); // to compress i EventDelay kChangeNoteDelay; // for random notes -Q8n0 octave_start_note = 42; -Q24n8 carrier_freq; // unsigned long with 24 integer bits and 8 fractional bits +UFix<7,0> octave_start_note = 42; // smooth transitions between notes -Smooth kSmoothFreq(0.85f); -int target_freq, smoothed_freq; +Smooth > kSmoothFreq(0.85f); +UFix<14,2> target_freq, smoothed_freq; //Optimization to have the frequencies on 16bits only. void setup(){ @@ -90,12 +90,12 @@ void updateControl(){ // change octave to midi 24 or any of 3 octaves above octave_start_note = (rand((byte)4)*12)+36; } - Q16n16 midi_note = Q8n0_to_Q16n16(octave_start_note+rndPentatonic()); - target_freq = Q16n16_to_Q16n0(Q16n16_mtof(midi_note)); // has to be 16 bits for Smooth + auto midi_note = octave_start_note + toUInt(rndPentatonic()); + target_freq = mtof(midi_note); // mtof return a UFix<16,16>, which is casted to UFix<14,2> (could overflow if the frequency is greater than 16kHz) kChangeNoteDelay.start(); } - smoothed_freq = kSmoothFreq.next(target_freq*4); // temporarily scale up target_freq to get better int smoothing at low values - aSin.setFreq(smoothed_freq/4); // then scale it back down after it's smoothed + smoothed_freq = kSmoothFreq.next(target_freq); // temporarily scale up target_freq to get better int smoothing at low values + aSin.setFreq(smoothed_freq); } diff --git a/examples/12.Misc/Shepard_Tone_HIFI/Shepard_Tone_HIFI.ino b/examples/12.Misc/Shepard_Tone_HIFI/Shepard_Tone_HIFI.ino index e3b3b71a2..153a12c04 100644 --- a/examples/12.Misc/Shepard_Tone_HIFI/Shepard_Tone_HIFI.ino +++ b/examples/12.Misc/Shepard_Tone_HIFI/Shepard_Tone_HIFI.ino @@ -33,6 +33,8 @@ Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later. */ + + #include #define MOZZI_AUDIO_MODE MOZZI_OUTPUT_2PIN_PWM #define MOZZI_CONTROL_RATE 128 @@ -40,29 +42,32 @@ #include #include #include -#include "triangle512_uint8.h" +#include #include #include #include -#include // for fixed-point fractional numbers +#include // reset and sync vol and freq controls each cycle EventDelay kTriggerDelay0; EventDelay kTriggerDelay1; -#define NOTE_CENTRE 60 -#define NOTE_RANGE 12 -#define NOTE_START_FIXEDPOINT float_to_Q16n16(NOTE_CENTRE + NOTE_RANGE) // so Line gliss has enough precision -#define NOTE_END_FIXEDPOINT float_to_Q16n16(NOTE_CENTRE - NOTE_RANGE) // so Line gliss has enough precision +const UFix<7,0> NOTE_CENTRE = 60, NOTE_RANGE = 12; +const UFix<7,0> NOTE_START_FIXEDPOINT = NOTE_CENTRE + NOTE_RANGE; +const UFix<7,0> NOTE_END_FIXEDPOINT = NOTE_CENTRE - NOTE_RANGE; #define GLISS_SECONDS 3.f +//#define CONTROL_STEPS_PER_GLISS ((unsigned int)((float)MOZZI_CONTROL_RATE * GLISS_SECONDS)) #define CONTROL_STEPS_PER_GLISS ((unsigned int)((float)MOZZI_CONTROL_RATE * GLISS_SECONDS)) + // use: Line lineName -// audio oscils -Line kGliss0; // Line to slide frequency -Line kGliss1; // Line to slide frequency +// The lines needs more precision than the notes, as they interpolate in between +// here we optimize by choosing the max precision to stay in 16bits (7+9=16) +// note that for an SFix, we would aim for 15 (plus a sign bit). +Line > kGliss0; // Line to slide frequency +Line > kGliss1; // Line to slide frequency // harmonics Oscil aCos0(SIN8192_DATA); @@ -73,12 +78,12 @@ Oscil kVol0(TRIANGLE512_DATA); Oscil kVol1(TRIANGLE512_DATA); // audio volumes updated each control interrupt and reused in audio -long v0, v1; +SFix<0,14> v0, v1; +//SFix<0,7> v0, v1; // micro-optimization void setup() { - //Serial.begin(115200); +//Serial.begin(115200); - // set volume change frequencies kVol0.setFreq(0.5f / GLISS_SECONDS); kVol1.setFreq(0.5f / GLISS_SECONDS); @@ -91,9 +96,8 @@ void setup() { startMozzi(); } - -void updateControl() { - +void updateControl() +{ if (kTriggerDelay0.ready()) { kGliss0.set(NOTE_START_FIXEDPOINT, NOTE_END_FIXEDPOINT, CONTROL_STEPS_PER_GLISS); kVol0.setPhase(0); @@ -101,39 +105,32 @@ void updateControl() { Serial.println("gliss1"); } - if (kTriggerDelay1.ready()) { + if (kTriggerDelay1.ready()) { kGliss1.set(NOTE_START_FIXEDPOINT, NOTE_END_FIXEDPOINT, CONTROL_STEPS_PER_GLISS); kVol1.setPhase(0); kTriggerDelay1.start((int)(GLISS_SECONDS * 1000.f)); // milliseconds Serial.println("\t gliss2"); } - // set harmonic frequencies - Q16n16 gliss0 = kGliss0.next(); // fixed point - float freq0 = Q16n16_to_float(Q16n16_mtof(gliss0)); // convert fixed point to floating point - - Q16n16 gliss1 = kGliss1.next(); // fixed point - float freq1 = Q16n16_to_float(Q16n16_mtof(gliss1)); // convert fixed point to floating point + auto gliss0 = kGliss0.next(); // fixed point + auto gliss1 = kGliss1.next(); // fixed point - aCos0.setFreq(freq0); - aCos1.setFreq(freq1); + aCos0.setFreq(mtof(gliss0)); + aCos1.setFreq(mtof(gliss1)); - v0 = kVol0.next(); - v1 = kVol1.next(); - // Serial.print((byte)v0); Serial.print("\t"); Serial.println((byte)v1); + v0 = toSFraction(kVol0.next()); // convert as a pure fractionnal between -1 and 1 + v1 = toSFraction(kVol1.next()); - // square for perceptual volume - v0 *= v0; - v1 *= v1; + // square for perceptual volume (also makes it positive...) + v0 = v0 * v0; + v1 = v1 * v1; } AudioOutput updateAudio() { - long asig = ((v0 * aCos0.next()) >> 8) + - ((v1 * aCos1.next()) >> 8); - return AudioOutput::fromNBit(17, asig); + auto asig = v0 * toSInt(aCos0.next()) + v1 * toSInt(aCos1.next()); + return AudioOutput::fromSFix(asig); // auto-scaling of the output with SFix } - void loop() { audioHook(); } diff --git a/examples/12.Misc/Shepard_Tone_HIFI/triangle512_uint8.h b/examples/12.Misc/Shepard_Tone_HIFI/triangle512_uint8.h deleted file mode 100644 index bd3d3e7c4..000000000 --- a/examples/12.Misc/Shepard_Tone_HIFI/triangle512_uint8.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef TRIANGLE512_H_ -#define TRIANGLE512_H_ - -#include -#include "mozzi_pgmspace.h" - -#define TRIANGLE512_NUM_CELLS 512 -#define TRIANGLE512_SAMPLERATE 512 - -CONSTTABLE_STORAGE(uint8_t) TRIANGLE512_DATA [] = {0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, -30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, -50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, -70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, -90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, -108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, -124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, -140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, -156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, -172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, -188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, -204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, -220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, -236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, -252, 253, 254, 255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, -242, 241, 240, 239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, -226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, 215, 214, 213, 212, 211, -210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 195, -194, 193, 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, -178, 177, 176, 175, 174, 173, 172, 171, 170, 169, 168, 167, 166, 165, 164, 163, -162, 161, 160, 159, 158, 157, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, -146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, -130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, -114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, -98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, -78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, -58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, -38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, -18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, }; - - #endif /* TRIANGLE512_H_ */ diff --git a/mozzi_midi.h b/mozzi_midi.h index 8f1a15573..e0e1e794c 100644 --- a/mozzi_midi.h +++ b/mozzi_midi.h @@ -24,6 +24,12 @@ class MidiToFreqPrivate { friend int mtof(uint8_t); friend int mtof(int); friend Q16n16 Q16n16_mtof(Q16n16); + template + friend UFix<16,16> mtof(UFix); + + template + friend UFix<16,16> mtof(SFix); + static CONSTTABLE_STORAGE(uint32_t) midiToFreq[128]; }; @@ -124,21 +130,52 @@ inline Q16n16 Q16n16_mtof(Q16n16 midival_fractional) return (Q16n16) (freq1+ diff_fraction); }; -inline UFix<16,16> mtof(UFix<16,16> midival) +/** @ingroup midi +Converts midi note number with speed and accuracy from a UFix<16,16>. +Uses Q16n16_mtof internally. +*/ +template +inline UFix<16,16> mtof(UFix<16,16,RANGE> midival) { return UFix<16,16>::fromRaw(Q16n16_mtof(midival.asRaw())); -} +}; -template - inline UFix<16,16> mtof(UFix midival) +/** @ingroup midi +Converts midi note number with speed and accuracy from any UFix. +Uses Q16n16_mtof internally. +*/ +template +inline UFix<16,16> mtof(UFix midival) { return UFix<16,16>::fromRaw(Q16n16_mtof(UFix<16,16>(midival).asRaw())); -} +}; -template - inline UFix<16,16> mtof(SFix midival) +/** @ingroup midi +Converts midi note number with speed and accuracy from any SFix. +Uses Q16n16_mtof internally. +*/ +template +inline UFix<16,16> mtof(SFix midival) { return UFix<16,16>::fromRaw(Q16n16_mtof(UFix<16,16>(midival).asRaw())); -} +}; + +/** @ingroup midi +Converts *whole* midi note number with speed and accuracy (more accurate that mtof(uint8_t)) +*/ +template +inline UFix<16,16> mtof(UFix midival) +{ + return UFix<16,16>::fromRaw((FLASH_OR_RAM_READ(MidiToFreqPrivate::midiToFreq + midival.asRaw()))); +}; + +/** @ingroup midi +Converts *whole* midi note number with speed and accuracy (more accurate that mtof(uint8_t)) +*/ +template +inline UFix<16,16> mtof(SFix midival) +{ + return UFix<16,16>::fromRaw((FLASH_OR_RAM_READ(MidiToFreqPrivate::midiToFreq + midival.asUFix().asRaw()))); +}; #endif /* MOZZI_MIDI_H_ */