@@ -0,0 +1,121 @@ | |||||
Creative Commons Legal Code | |||||
CC0 1.0 Universal | |||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE | |||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN | |||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS | |||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES | |||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS | |||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM | |||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED | |||||
HEREUNDER. | |||||
Statement of Purpose | |||||
The laws of most jurisdictions throughout the world automatically confer | |||||
exclusive Copyright and Related Rights (defined below) upon the creator | |||||
and subsequent owner(s) (each and all, an "owner") of an original work of | |||||
authorship and/or a database (each, a "Work"). | |||||
Certain owners wish to permanently relinquish those rights to a Work for | |||||
the purpose of contributing to a commons of creative, cultural and | |||||
scientific works ("Commons") that the public can reliably and without fear | |||||
of later claims of infringement build upon, modify, incorporate in other | |||||
works, reuse and redistribute as freely as possible in any form whatsoever | |||||
and for any purposes, including without limitation commercial purposes. | |||||
These owners may contribute to the Commons to promote the ideal of a free | |||||
culture and the further production of creative, cultural and scientific | |||||
works, or to gain reputation or greater distribution for their Work in | |||||
part through the use and efforts of others. | |||||
For these and/or other purposes and motivations, and without any | |||||
expectation of additional consideration or compensation, the person | |||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she | |||||
is an owner of Copyright and Related Rights in the Work, voluntarily | |||||
elects to apply CC0 to the Work and publicly distribute the Work under its | |||||
terms, with knowledge of his or her Copyright and Related Rights in the | |||||
Work and the meaning and intended legal effect of CC0 on those rights. | |||||
1. Copyright and Related Rights. A Work made available under CC0 may be | |||||
protected by copyright and related or neighboring rights ("Copyright and | |||||
Related Rights"). Copyright and Related Rights include, but are not | |||||
limited to, the following: | |||||
i. the right to reproduce, adapt, distribute, perform, display, | |||||
communicate, and translate a Work; | |||||
ii. moral rights retained by the original author(s) and/or performer(s); | |||||
iii. publicity and privacy rights pertaining to a person's image or | |||||
likeness depicted in a Work; | |||||
iv. rights protecting against unfair competition in regards to a Work, | |||||
subject to the limitations in paragraph 4(a), below; | |||||
v. rights protecting the extraction, dissemination, use and reuse of data | |||||
in a Work; | |||||
vi. database rights (such as those arising under Directive 96/9/EC of the | |||||
European Parliament and of the Council of 11 March 1996 on the legal | |||||
protection of databases, and under any national implementation | |||||
thereof, including any amended or successor version of such | |||||
directive); and | |||||
vii. other similar, equivalent or corresponding rights throughout the | |||||
world based on applicable law or treaty, and any national | |||||
implementations thereof. | |||||
2. Waiver. To the greatest extent permitted by, but not in contravention | |||||
of, applicable law, Affirmer hereby overtly, fully, permanently, | |||||
irrevocably and unconditionally waives, abandons, and surrenders all of | |||||
Affirmer's Copyright and Related Rights and associated claims and causes | |||||
of action, whether now known or unknown (including existing as well as | |||||
future claims and causes of action), in the Work (i) in all territories | |||||
worldwide, (ii) for the maximum duration provided by applicable law or | |||||
treaty (including future time extensions), (iii) in any current or future | |||||
medium and for any number of copies, and (iv) for any purpose whatsoever, | |||||
including without limitation commercial, advertising or promotional | |||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each | |||||
member of the public at large and to the detriment of Affirmer's heirs and | |||||
successors, fully intending that such Waiver shall not be subject to | |||||
revocation, rescission, cancellation, termination, or any other legal or | |||||
equitable action to disrupt the quiet enjoyment of the Work by the public | |||||
as contemplated by Affirmer's express Statement of Purpose. | |||||
3. Public License Fallback. Should any part of the Waiver for any reason | |||||
be judged legally invalid or ineffective under applicable law, then the | |||||
Waiver shall be preserved to the maximum extent permitted taking into | |||||
account Affirmer's express Statement of Purpose. In addition, to the | |||||
extent the Waiver is so judged Affirmer hereby grants to each affected | |||||
person a royalty-free, non transferable, non sublicensable, non exclusive, | |||||
irrevocable and unconditional license to exercise Affirmer's Copyright and | |||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the | |||||
maximum duration provided by applicable law or treaty (including future | |||||
time extensions), (iii) in any current or future medium and for any number | |||||
of copies, and (iv) for any purpose whatsoever, including without | |||||
limitation commercial, advertising or promotional purposes (the | |||||
"License"). The License shall be deemed effective as of the date CC0 was | |||||
applied by Affirmer to the Work. Should any part of the License for any | |||||
reason be judged legally invalid or ineffective under applicable law, such | |||||
partial invalidity or ineffectiveness shall not invalidate the remainder | |||||
of the License, and in such case Affirmer hereby affirms that he or she | |||||
will not (i) exercise any of his or her remaining Copyright and Related | |||||
Rights in the Work or (ii) assert any associated claims and causes of | |||||
action with respect to the Work, in either case contrary to Affirmer's | |||||
express Statement of Purpose. | |||||
4. Limitations and Disclaimers. | |||||
a. No trademark or patent rights held by Affirmer are waived, abandoned, | |||||
surrendered, licensed or otherwise affected by this document. | |||||
b. Affirmer offers the Work as-is and makes no representations or | |||||
warranties of any kind concerning the Work, express, implied, | |||||
statutory or otherwise, including without limitation warranties of | |||||
title, merchantability, fitness for a particular purpose, non | |||||
infringement, or the absence of latent or other defects, accuracy, or | |||||
the present or absence of errors, whether or not discoverable, all to | |||||
the greatest extent permissible under applicable law. | |||||
c. Affirmer disclaims responsibility for clearing rights of other persons | |||||
that may apply to the Work or any use thereof, including without | |||||
limitation any person's Copyright and Related Rights in the Work. | |||||
Further, Affirmer disclaims responsibility for obtaining any necessary | |||||
consents, permissions or other rights required for any use of the | |||||
Work. | |||||
d. Affirmer understands and acknowledges that Creative Commons is not a | |||||
party to this document and has no duty or obligation with respect to | |||||
this CC0 or use of the Work. |
@@ -0,0 +1,38 @@ | |||||
SLUG = FrozenWasteland | |||||
VERSION = 0.6.7 | |||||
# FLAGS will be passed to both the C and C++ compiler | |||||
FLAGS += \ | |||||
-DTEST \ | |||||
-I./eurorack \ | |||||
-I./src/dsp-delay \ | |||||
-I./src/dsp-filter/utils -I./src/dsp-filter/filters -I./src/dsp-filter/third-party/falco \ | |||||
-Wno-unused-local-typedefs | |||||
CFLAGS += | |||||
CXXFLAGS += | |||||
# Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. | |||||
# Static libraries are fine. | |||||
LDFLAGS += | |||||
# Add .cpp and .c files to the build | |||||
#SOURCES += eurorack/stmlib/utils/random.cc | |||||
#SOURCES += eurorack/stmlib/dsp/atan.cc | |||||
#SOURCES += eurorack/stmlib/dsp/units.cc | |||||
#SOURCES += eurorack/clouds/dsp/correlator.cc | |||||
#SOURCES += eurorack/clouds/dsp/granular_processor.cc | |||||
#SOURCES += eurorack/clouds/dsp/mu_law.cc | |||||
#SOURCES += eurorack/clouds/dsp/pvoc/frame_transformation.cc | |||||
#SOURCES += eurorack/clouds/dsp/pvoc/phase_vocoder.cc | |||||
#SOURCES += eurorack/clouds/dsp/pvoc/stft.cc | |||||
#SOURCES += eurorack/clouds/resources.cc | |||||
SOURCES += $(wildcard src/*.cpp src/filters/*.cpp src/dsp-noise/*.cpp src/dsp-filter/*.cpp rc/dsp-delay/*.hpp src/stmlib/*.cc) | |||||
# Add files to the ZIP package when running `make dist` | |||||
# The compiled plugin is automatically added. | |||||
DISTRIBUTABLES += $(wildcard LICENSE*) res | |||||
# Include the VCV plugin Makefile framework | |||||
RACK_DIR ?= ../.. | |||||
include $(RACK_DIR)/plugin.mk |
@@ -0,0 +1,225 @@ | |||||
# Frozen Wasteland VCV plugins | |||||
A collection of unusual plugins that will add a certain coolness to your patches. | |||||
## BPM LFO | |||||
 | |||||
- Tempo Sync'd LFO. | |||||
- CV Control of Time Division | |||||
- Pairs well with the Quad Euclidean Rhythm and Quad Golomb Ruler Rhythm | |||||
- When Holding is active, the Outputs stay at last value | |||||
- Hold input can either be a gate (which switches each time) or momentary (active while signal is positve) | |||||
- If set to Free, LFO still runs while being held (even if outputs don't change), Pause causes LFO to pause. | |||||
## BPM LFO 2 | |||||
 | |||||
- Variation of original BPM LFO. | |||||
- CV Control of Time Division and Wave Shape | |||||
- Can either be variable sawtooth/triangle or variable duty cycle square wave | |||||
## Damian Lillard | |||||
 | |||||
- Voltage Controlled Quad Crossover | |||||
- Use Sends/Returns to apply FX to different frequency domains of input signal | |||||
- Basic example, distort only the mid lows and/or mid highs so your distortion doesn't remove bottom end | |||||
- or, apply different delays to create interesting resonances and other FX | |||||
- Video of a snare drum being fed into four delay lines: https://www.youtube.com/watch?v=EB7A_hzMpNI | |||||
- Use your imagination! | |||||
## Everlasting Glottal Stopper | |||||
 | |||||
- Based on the Rosenburg model of human's larynx | |||||
- Pairs well with the Vox Inhumana | |||||
- Can be a bit fussy to create harmonically "rich" waves, but playing with the "Closed" setting can find some sweet spots | |||||
- Use the Noise parameter to add a "breathy" quality to wave | |||||
## Hair Pick | |||||
 | |||||
- Based on the comb filter section of Intellijel/Cylonix Rainmaker | |||||
- Best bet is to refer to this: https://intellijel.com/wp-content/uploads/2016/01/rainmaker_manual-v109.pdf | |||||
- Comb filter size is either based on Clock & Division or Size Parameter - patching a clock in disables Size knob | |||||
- V/Oct works using either method | |||||
- The Size CV can change size +/- 10% using both Clock or Size | |||||
- There are 16 comb patterns, the last 8 are randomly perturbed versions of the first 8 | |||||
- The Tap count can vary the number of taps between 1 and 64. They are deactivated in a pattern shown in the rainmaker manual | |||||
- The Edge Level, Tent Level and Tent Tap control the overall volume of the taps. | |||||
- Feedback Type can add non-linearity and exponential decay. Clarinet mode is same as guitar for now. | |||||
- The size out allows the comb's length to control other modules (say the feedback delay time in Portland Weather) | |||||
## Lissajou LFO. | |||||
 | |||||
- Loosely based on ADDAC Systems now discontinued 502 module https://www.modulargrid.net/e/addac-system-addac502-ultra-lissajous | |||||
- Each LFO is actually a pair of LFOs (x and y) | |||||
- Adjusting them will show harmonic relationship between the two | |||||
- Yellow is LFO 1, Blue is LFO 2 | |||||
- Output 1: (x1 + x2) / 2 | |||||
- Output 2: (y1 + y2) / 2 | |||||
- Output 3: (x1 + y1 + x2 + y2) / 4 | |||||
- Output 4: x1/x2 | |||||
- Output 5: y1/y2 | |||||
## Mr. Blue Sky | |||||
 | |||||
- Yes, I love ELO | |||||
- This is shamelessly based on Sebastien Bouffier (bid°°)'s fantastic zINC vocoder | |||||
- Generally you patch something "synthy" to the carrier input | |||||
- The voice sample (or I like to think "harmonically interesting" source) gets patched into the Mod input | |||||
- Each modulator band is normalled to its respective carrier input, but the patch points allow you to have different bands modulate different carrier bands | |||||
- You can patch in effects (a delay, perhaps?) between the mod out and carrier in. | |||||
- CV Control of over almost everything. I highly recommend playing with the band offset. | |||||
- You can either CV the value of the band offset, or send triggers to the + and - Inputs to increment/decrement the offset | |||||
## The One Ring (modulator) | |||||
 | |||||
- Ring modulation essentially multiplies the two frequencies together, creating non-harmonic sidebands | |||||
- Doing RM digitally is easy - just multiply the two signals. But it was tougher to do with an analog circuit. The traditional design used 4 diodes. | |||||
- This module is based on this paper: http://recherche.ircam.fr/pub/dafx11/Papers/66_e.pdf which describes how to model an analog ring modulator with all its quirks | |||||
- Let's you simulate different types of diode response as used in analog ring modulators | |||||
- Bias is the input voltage level where the diode responds - below that the diode outputs 0V | |||||
- Linear is the input voltage level where the diode outputs a linear response. Between the Bias and Linar levels, the diode acts 'weird' or let's say 'harmonically interesting' | |||||
- Slope is the strength of the diode output | |||||
- The graph display is useful for seeing the diodes' voltage responses | |||||
- CV control over everything | |||||
### Ring Modulation | |||||
- Ring Modulation has always appealed to the math nerd in me, because while the process is pretty straightfoward, the results are really cool. | |||||
- example. If you feed into a RM a 200hz sine wave and a 500hz sine wave, the output is the sum and difference of those two frequencies, so 300hz and 700hz. | |||||
- Where it gets crazy is when harmonics are involved since the output is the sum and difference of "all" the frequencies. example, let's say you had the 200 and 500hz sine waves, but they both each had a 2nd harmonic as well, so a 400hz and 1000hz. The output would be: 500+200=700,1000+200=1200,500+400=900,1000+400=1400,500-200=300,500-400=100,1000-300=700(again),1000-400=600. So a whole slew of new frequencies got created. | |||||
- Most waveforms have *way* more harmonics than this, so the results are really crazy. | |||||
- The Dalek Voices in the original Dr. Who were done using ring modulators. Eliminate!!! | |||||
## Phased Locked Loop (PLL) | |||||
 | |||||
- Inspired by Doepfer's A-196 PLL module | |||||
- This is a very weird module and can be kind of "fussy". Recommend reading http://www.doepfer.de/A196.htm | |||||
- Added CV control of the LPF that the 196 did not have | |||||
- Added Pulse Width and Pulse Width Modulation of the internal VCO | |||||
- Generally you want to "listen" to the **SQR OUT** | |||||
- You'll want to feed a Square-ish wave into Signal In | |||||
- Low **LPF FREQ** settings create a warbling effect, high = craziness | |||||
- **EXTERNAL IN** overrides interal VCO | |||||
- The **LPF OUT** is normalled to the **VCO CV**, try patching something in between the two | |||||
- Two comparator modes: XOR and D type Flip Flop | |||||
- Does not make pretty sounds, but can be a lot of fun. | |||||
## Portland Weather | |||||
 | |||||
- Based on the rhythmic tap delay section of Intellijel/Cylonix Rainmaker | |||||
### This is a crazy deep module and is best just to play with. | |||||
### Since it is a "rhythmic" delay, short duration or percussive sounds generally work best | |||||
- Best bet is to refer to this: https://intellijel.com/wp-content/uploads/2016/01/rainmaker_manual-v109.pdf | |||||
- Delay time is either based on Clock & Division or Size Parameter - patching a clock in disables Size knob | |||||
- There are 16 delay groove patterns. The groove amount balances the delay time between the groove or straight time | |||||
- Stacking a tap makes the tap use the delay time of its neighbor to right. If multiple taps are stacked the right most tap controls time | |||||
- Stacking is useful for making chords | |||||
- Each tap has its own level, pan, filter type, cutoff, Q, pitch shift and pitch detune control | |||||
- The left and right channels can independently use a tap # to determine where feedback comes from (the feedback bypasses the filter and pitchshifting) | |||||
- The feedback slip controls allow the feedback to be ahead/behind the tap time for dragging effects, etc. | |||||
- If feedback tap is set to All, the mix of all 16 taps is fed back (use cautiously) | |||||
- If feedback tap is set to Ext, the feedback time input is used. Useful for sycing with say, the Hair Pick | |||||
- The Grain Number and Grain Size control how the all the pitch shifters work. "RAW" is uses one grain sample but without the a triangle window | |||||
- Reverse mode reverses direction of input buffer - time is based on feedback time | |||||
- Ping Pong feeds the L feedback into the right channel and vise versa | |||||
- FB Send and Returns allow you to insert FX into the feedback loop | |||||
## Quad Euclidean Rhythm | |||||
 | |||||
- 4 Euclidiean rhythm based triggers | |||||
- CV control of Steps, Divisions and Offset, Padding, Accents and Accent Rotation | |||||
- QERs can be chained together to create arbitrarily long sequences. | |||||
- If Chain Mode is Boss, the QER runs on start up, then stops if the track's Start input is patched, until a Start trigger is received - basically the first QER should be set to this | |||||
- If Chain Mode is Employee, the QER track will be idle until a Start trigger is received. | |||||
- Patch EoC (End of Cycle) outputs to next QER's Start Inputs | |||||
- Last QER's EoC should be patched back to first QER's Start Input to create a complete loop. | |||||
- May want to consider using an OR module (Qwelk has a nice one) so that mutiple QER's outs and accent outs can gate a single unit | |||||
- Each QER has its own clock input, so tempo changes can easily be created | |||||
- Mute Input keeps rhythm running just no output | |||||
- https://www.youtube.com/watch?v=ARMxz11z9FU is an example of how to patch a couple QERs together and drive some drum synths | |||||
- Normally each track advances one step every clock beat and are independent. If Time Sync is enabled, the track with the most steps becomes the master and the other tracks will fit their patterns to the timing of the master. This allows for complex polyrhythms (ie. 15 on 13 on 11 on 4, etc.) | |||||
- https://www.youtube.com/watch?v=eCErJMKAlVY is an example of the QER with Time Sync enabled | |||||
### Euclidean Rhythms | |||||
- Euclidean are based upon attempting to equally distribute the divisions among the steps available | |||||
- Basic example, with a step count of 16, and 2 divisions, the divisions will be on the 1 and the 9. | |||||
- A division of 4 would give you a basic 4 on the floor rhythm | |||||
- The Offset control lets you move the starting point of the rhythm to something other than the 1 | |||||
## Quad Golumb Ruler Rhythm | |||||
 | |||||
- 4 Golumb Ruler rhythm based triggers | |||||
- For when QER is "too danceable" :) | |||||
- Features identical to the Quad Euclidean Rhythm and both units can be chained together | |||||
- 18 step sequencer since Golomb Ruler Rhythms like prime numbers having more than 17 steps allows for more patterns | |||||
### Golomb Ruler Rhythms | |||||
- Unlike Euclidean Rhythms which seek to evenly distribute the divisions, Golomb Rulers try to ensure unequal distribution | |||||
- Basic example, with a step count of 16, and 4 divisions, the divisions will be on the 1,4,9 and 13. | |||||
## Quantussy Cell | |||||
 | |||||
- This is based on work by Peter Blasser and Richard Brewster. | |||||
- Instantiate any number of cells (odd numbers work best - say 5 or 7) | |||||
- This is what is called a **Quantussy Ring** | |||||
 | |||||
- The Freq control sets the baseline value of the internal LFO | |||||
- The Castle output should be connected to the next Cell's CV Input. | |||||
- One of the outputs (usually triangle or saw) should be connected to the next Cell's Castle input | |||||
- Repeat for each cell. The last cell is connected back to the first cell | |||||
- Use any of the remaining wav outputs from any cell to provide semi-random bordering on chaotic CV | |||||
- Check out http://pugix.com/synth/eurorack-quantussy-cells/ | |||||
## Roulette LFO | |||||
 | |||||
- Based on the concept of a couple different types of Roulettes: https://en.wikipedia.org/wiki/Roulette_(curve) | |||||
- A circle rolling either inside a bigger circle (Hypotrochoid) or circle rolling outside a bigger circle (Epitrochoid) | |||||
- The ratio of the Big R to small r can create interesting patterns | |||||
- d is the distance of the 'pen' from the center of small circle. | |||||
- if d = r the shapes become a special form of the shapes - either a Hypocycloid or Epicycloid | |||||
## Seriously Slow LFO | |||||
 | |||||
- Waiting for the next Ice Age? Tidal Modulator too fast paced? This is the LFO for you. | |||||
- Generate oscillating CVs with range from 1 minute to 100 months | |||||
- NOTE: Pretty sure my math is correct, but 100 month LFOs have not been unit tested | |||||
## Vox Inhumana | |||||
 | |||||
- Generates vowel-ish sounds when given harmonically rich sources | |||||
- Pairs well with the Everlasting Glottal Stopper, but sawtooth and pulse waves work well | |||||
- Vowel/Voice formants are based on https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html | |||||
- Fc allows changing the frequency of all formants at once, over a large range | |||||
- The CV of a formant allows a +/- 50% change of base vowel frequency | |||||
- The CV of amplitude allows the base level of the vowel/voice to be modified by about 2x. | |||||
- Changing the Fc of formants 1 & 2 can make the vowel sound more long or short | |||||
## Contributing | |||||
I welcome Issues and Pull Requests to this repository if you have suggestions for improvement. | |||||
These plugins are released into the public domain ([CC0](https://creativecommons.org/publicdomain/zero/1.0/)). |
@@ -0,0 +1,304 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
#include "stmlib/system/bootloader_utils.h" | |||||
#include "stmlib/system/system_clock.h" | |||||
#include "clouds/drivers/codec.h" | |||||
#include "clouds/drivers/leds.h" | |||||
#include "clouds/drivers/switches.h" | |||||
#include "clouds/drivers/system.h" | |||||
#include "clouds/drivers/version.h" | |||||
#include "clouds/meter.h" | |||||
#include "stm_audio_bootloader/qpsk/packet_decoder.h" | |||||
#include "stm_audio_bootloader/qpsk/demodulator.h" | |||||
#include <cstring> | |||||
using namespace clouds; | |||||
using namespace stmlib; | |||||
using namespace stm_audio_bootloader; | |||||
const double kSampleRate = 48000.0; | |||||
const double kModulationRate = 6000.0; | |||||
const double kBitRate = 12000.0; | |||||
const uint32_t kStartAddress = 0x08008000; | |||||
Codec codec; | |||||
Meter meter; | |||||
Leds leds; | |||||
Switches switches; | |||||
PacketDecoder decoder; | |||||
Demodulator demodulator; | |||||
int __errno; | |||||
// Default interrupt handlers. | |||||
extern "C" { | |||||
void NMI_Handler() { } | |||||
void HardFault_Handler() { while (1); } | |||||
void MemManage_Handler() { while (1); } | |||||
void BusFault_Handler() { while (1); } | |||||
void UsageFault_Handler() { while (1); } | |||||
void SVC_Handler() { } | |||||
void DebugMon_Handler() { } | |||||
void PendSV_Handler() { } | |||||
} | |||||
extern "C" { | |||||
const int16_t lut_db[] = { | |||||
-32768, -32768, -24576, -19783, -16384, -13746, -11591, -9770, | |||||
-8192, -6799, -5554, -4428, -3399, -2453, -1578, -762, | |||||
0, 716, 1392, 2031, 2637, 3213, 3763, 4289, | |||||
4792, 5274, 5738, 6184, 6613, 7028, 7429, 7816, | |||||
8192, 8555, 8908, 9251, 9584, 9907, 10223, 10530, | |||||
10829, 11121, 11405, 11683, 11955, 12221, 12481, 12735, | |||||
12984, 13227, 13466, 13700, 13930, 14155, 14376, 14592, | |||||
14805, 15015, 15220, 15422, 15621, 15816, 16008, 16197, | |||||
16384, 16567, 16747, 16925, 17100, 17273, 17443, 17610, | |||||
17776, 17939, 18099, 18258, 18415, 18569, 18722, 18872, | |||||
19021, 19168, 19313, 19456, 19597, 19737, 19875, 20012, | |||||
20147, 20281, 20413, 20543, 20673, 20800, 20927, 21052, | |||||
21176, 21298, 21419, 21539, 21658, 21776, 21892, 22007, | |||||
22122, 22235, 22347, 22458, 22568, 22676, 22784, 22891, | |||||
22997, 23102, 23207, 23310, 23412, 23514, 23614, 23714, | |||||
23813, 23911, 24008, 24105, 24200, 24295, 24389, 24483, | |||||
24576, 24667, 24759, 24849, 24939, 25028, 25117, 25205, | |||||
25292, 25379, 25465, 25550, 25635, 25719, 25802, 25885, | |||||
25968, 26049, 26131, 26211, 26291, 26371, 26450, 26529, | |||||
26607, 26684, 26761, 26838, 26914, 26989, 27064, 27139, | |||||
27213, 27286, 27360, 27432, 27505, 27576, 27648, 27719, | |||||
27789, 27860, 27929, 27999, 28067, 28136, 28204, 28272, | |||||
28339, 28406, 28473, 28539, 28605, 28670, 28735, 28800, | |||||
28865, 28929, 28992, 29056, 29119, 29181, 29244, 29306, | |||||
29368, 29429, 29490, 29551, 29611, 29671, 29731, 29791, | |||||
29850, 29909, 29968, 30026, 30084, 30142, 30199, 30257, | |||||
30314, 30370, 30427, 30483, 30539, 30594, 30650, 30705, | |||||
30760, 30814, 30868, 30923, 30976, 31030, 31083, 31136, | |||||
31189, 31242, 31294, 31347, 31399, 31450, 31502, 31553, | |||||
31604, 31655, 31706, 31756, 31806, 31856, 31906, 31955, | |||||
32005, 32054, 32103, 32152, 32200, 32248, 32297, 32345, | |||||
32392, 32440, 32487, 32534, 32581, 32628, 32675, 32721, | |||||
32721, | |||||
}; | |||||
enum UiState { | |||||
UI_STATE_WAITING, | |||||
UI_STATE_RECEIVING, | |||||
UI_STATE_ERROR, | |||||
UI_STATE_WRITING | |||||
}; | |||||
volatile bool switch_released = false; | |||||
volatile UiState ui_state; | |||||
void UpdateLeds() { | |||||
leds.Clear(); | |||||
leds.set_freeze(true); | |||||
switch (ui_state) { | |||||
case UI_STATE_WAITING: | |||||
leds.set_freeze(system_clock.milliseconds() & 128); | |||||
break; | |||||
case UI_STATE_RECEIVING: | |||||
leds.set_freeze(system_clock.milliseconds() & 32); | |||||
leds.PaintBar(lut_db[meter.peak() >> 7]); | |||||
break; | |||||
case UI_STATE_ERROR: | |||||
{ | |||||
bool on = system_clock.milliseconds() & 256; | |||||
for (uint8_t i = 0; i < 4; ++i) { | |||||
leds.set_status(i, on ? 255 : 0, 0); | |||||
} | |||||
} | |||||
break; | |||||
case UI_STATE_WRITING: | |||||
{ | |||||
for (uint8_t i = 0; i < 4; ++i) { | |||||
leds.set_status(i, 0, 255); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
leds.Write(); | |||||
} | |||||
void SysTick_Handler() { | |||||
system_clock.Tick(); | |||||
switches.Debounce(); | |||||
if (switches.released(2)) { | |||||
switch_released = true; | |||||
} | |||||
UpdateLeds(); | |||||
} | |||||
} | |||||
size_t discard_samples = 8000; | |||||
void FillBuffer(Codec::Frame* input, Codec::Frame* output, size_t n) { | |||||
meter.Process(input, n); | |||||
while (n--) { | |||||
int32_t sample = (input->l >> 4) + 2048; | |||||
if (!discard_samples) { | |||||
demodulator.PushSample(sample); | |||||
} else { | |||||
--discard_samples; | |||||
} | |||||
*output = *input; | |||||
++output; | |||||
++input; | |||||
} | |||||
} | |||||
static size_t current_address; | |||||
static uint16_t packet_index; | |||||
static uint32_t kSectorBaseAddress[] = { | |||||
0x08000000, | |||||
0x08004000, | |||||
0x08008000, | |||||
0x0800C000, | |||||
0x08010000, | |||||
0x08020000, | |||||
0x08040000, | |||||
0x08060000, | |||||
0x08080000, | |||||
0x080A0000, | |||||
0x080C0000, | |||||
0x080E0000 | |||||
}; | |||||
const uint32_t kBlockSize = 16384; | |||||
const uint16_t kPacketsPerBlock = kBlockSize / kPacketSize; | |||||
uint8_t rx_buffer[kBlockSize]; | |||||
void ProgramPage(const uint8_t* data, size_t size) { | |||||
FLASH_Unlock(); | |||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | | |||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR); | |||||
for (int32_t i = 0; i < 12; ++i) { | |||||
if (current_address == kSectorBaseAddress[i]) { | |||||
FLASH_EraseSector(i * 8, VoltageRange_3); | |||||
} | |||||
} | |||||
const uint32_t* words = static_cast<const uint32_t*>( | |||||
static_cast<const void*>(data)); | |||||
for (size_t written = 0; written < size; written += 4) { | |||||
FLASH_ProgramWord(current_address, *words++); | |||||
current_address += 4; | |||||
} | |||||
} | |||||
void Init() { | |||||
System sys; | |||||
Version version; | |||||
sys.Init(false); | |||||
leds.Init(); | |||||
meter.Init(48000); | |||||
switches.Init(); | |||||
version.Init(); | |||||
if (!codec.Init(!version.revised(), 48000)) { } | |||||
if (!codec.Start(32, &FillBuffer)) { } | |||||
sys.StartTimers(); | |||||
} | |||||
void InitializeReception() { | |||||
decoder.Init(20000); | |||||
demodulator.Init( | |||||
kModulationRate / kSampleRate * 4294967296.0, | |||||
kSampleRate / kModulationRate, | |||||
2.0 * kSampleRate / kBitRate); | |||||
demodulator.SyncCarrier(true); | |||||
decoder.Reset(); | |||||
current_address = kStartAddress; | |||||
packet_index = 0; | |||||
ui_state = UI_STATE_WAITING; | |||||
} | |||||
int main(void) { | |||||
InitializeReception(); | |||||
Init(); | |||||
bool exit_updater = !switches.pressed_immediate(2); | |||||
while (!exit_updater) { | |||||
bool error = false; | |||||
if (demodulator.state() == DEMODULATOR_STATE_OVERFLOW) { | |||||
error = true; | |||||
} else { | |||||
demodulator.ProcessAtLeast(32); | |||||
} | |||||
while (demodulator.available() && !error && !exit_updater) { | |||||
uint8_t symbol = demodulator.NextSymbol(); | |||||
PacketDecoderState state = decoder.ProcessSymbol(symbol); | |||||
switch (state) { | |||||
case PACKET_DECODER_STATE_OK: | |||||
{ | |||||
ui_state = UI_STATE_RECEIVING; | |||||
memcpy( | |||||
rx_buffer + (packet_index % kPacketsPerBlock) * kPacketSize, | |||||
decoder.packet_data(), | |||||
kPacketSize); | |||||
++packet_index; | |||||
if ((packet_index % kPacketsPerBlock) == 0) { | |||||
ui_state = UI_STATE_WRITING; | |||||
ProgramPage(rx_buffer, kBlockSize); | |||||
decoder.Reset(); | |||||
demodulator.SyncCarrier(false); | |||||
} else { | |||||
decoder.Reset(); | |||||
demodulator.SyncDecision(); | |||||
} | |||||
} | |||||
break; | |||||
case PACKET_DECODER_STATE_ERROR_SYNC: | |||||
case PACKET_DECODER_STATE_ERROR_CRC: | |||||
error = true; | |||||
break; | |||||
case PACKET_DECODER_STATE_END_OF_TRANSMISSION: | |||||
exit_updater = true; | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
if (error) { | |||||
ui_state = UI_STATE_ERROR; | |||||
switch_released = false; | |||||
while (!switch_released); // Polled in ISR | |||||
InitializeReception(); | |||||
} | |||||
} | |||||
codec.Stop(); | |||||
Uninitialize(); | |||||
JumpTo(kStartAddress); | |||||
while (1) { } | |||||
} |
@@ -0,0 +1,47 @@ | |||||
# Copyright 2014 Olivier Gillet. | |||||
# | |||||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
# THE SOFTWARE. | |||||
# | |||||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||||
# System specifications | |||||
F_CRYSTAL = 8000000L | |||||
F_CPU = 168000000L | |||||
SYSCLOCK = SYSCLK_FREQ_168MHz | |||||
FAMILY = f4xx | |||||
# USB = enabled | |||||
# Preferred upload command | |||||
UPLOAD_COMMAND = upload_jtag_erase_first | |||||
# Packages to build | |||||
TARGET = clouds_bootloader | |||||
PACKAGES = clouds/bootloader \ | |||||
clouds/drivers \ | |||||
stm_audio_bootloader/qpsk \ | |||||
stmlib/dsp \ | |||||
stmlib/utils \ | |||||
stmlib/system | |||||
RESOURCES = clouds/resources | |||||
TOOLCHAIN_PATH ?= /usr/local/arm-4.8.3/ | |||||
include stmlib/makefile.inc |
@@ -0,0 +1,137 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
#include "clouds/cv_scaler.h" | |||||
#include "clouds/drivers/codec.h" | |||||
#include "clouds/drivers/debug_pin.h" | |||||
#include "clouds/drivers/debug_port.h" | |||||
#include "clouds/drivers/system.h" | |||||
#include "clouds/drivers/version.h" | |||||
#include "clouds/dsp/granular_processor.h" | |||||
#include "clouds/meter.h" | |||||
#include "clouds/resources.h" | |||||
#include "clouds/settings.h" | |||||
#include "clouds/ui.h" | |||||
// #define PROFILE_INTERRUPT 1 | |||||
using namespace clouds; | |||||
using namespace stmlib; | |||||
GranularProcessor processor; | |||||
Codec codec; | |||||
DebugPort debug_port; | |||||
CvScaler cv_scaler; | |||||
Meter meter; | |||||
Settings settings; | |||||
Ui ui; | |||||
// Pre-allocate big blocks in main memory and CCM. No malloc here. | |||||
uint8_t block_mem[118784]; | |||||
uint8_t block_ccm[65536 - 128] __attribute__ ((section (".ccmdata"))); | |||||
int __errno; | |||||
// Default interrupt handlers. | |||||
extern "C" { | |||||
void NMI_Handler() { } | |||||
void HardFault_Handler() { while (1); } | |||||
void MemManage_Handler() { while (1); } | |||||
void BusFault_Handler() { while (1); } | |||||
void UsageFault_Handler() { while (1); } | |||||
void SVC_Handler() { } | |||||
void DebugMon_Handler() { } | |||||
void PendSV_Handler() { } | |||||
} | |||||
extern "C" { | |||||
void SysTick_Handler() { | |||||
ui.Poll(); | |||||
if (settings.freshly_baked()) { | |||||
if (debug_port.readable()) { | |||||
uint8_t command = debug_port.Read(); | |||||
uint8_t response = ui.HandleFactoryTestingRequest(command); | |||||
debug_port.Write(response); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void FillBuffer(Codec::Frame* input, Codec::Frame* output, size_t n) { | |||||
#ifdef PROFILE_INTERRUPT | |||||
TIC | |||||
#endif // PROFILE_INTERRUPT | |||||
cv_scaler.Read(processor.mutable_parameters()); | |||||
processor.Process((ShortFrame*)input, (ShortFrame*)output, n); | |||||
meter.Process(processor.parameters().freeze ? output : input, n); | |||||
#ifdef PROFILE_INTERRUPT | |||||
TOC | |||||
#endif // PROFILE_INTERRUPT | |||||
} | |||||
void Init() { | |||||
System sys; | |||||
Version version; | |||||
sys.Init(true); | |||||
version.Init(); | |||||
// Init granular processor. | |||||
processor.Init( | |||||
block_mem, sizeof(block_mem), | |||||
block_ccm, sizeof(block_ccm)); | |||||
settings.Init(); | |||||
cv_scaler.Init(settings.mutable_calibration_data()); | |||||
meter.Init(32000); | |||||
ui.Init(&settings, &cv_scaler, &processor, &meter); | |||||
bool master = !version.revised(); | |||||
if (!codec.Init(master, 32000)) { | |||||
ui.Panic(); | |||||
} | |||||
if (!codec.Start(32, &FillBuffer)) { | |||||
ui.Panic(); | |||||
} | |||||
if (settings.freshly_baked()) { | |||||
#ifdef PROFILE_INTERRUPT | |||||
DebugPin::Init(); | |||||
#else | |||||
debug_port.Init(); | |||||
#endif // PROFILE_INTERRUPT | |||||
} | |||||
sys.StartTimers(); | |||||
} | |||||
int main(void) { | |||||
Init(); | |||||
while (1) { | |||||
ui.DoEvents(); | |||||
processor.Prepare(); | |||||
} | |||||
} |
@@ -0,0 +1,215 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Calibration settings. | |||||
#include "clouds/cv_scaler.h" | |||||
#include <algorithm> | |||||
#include <cmath> | |||||
#include "stmlib/dsp/dsp.h" | |||||
#include "clouds/resources.h" | |||||
namespace clouds { | |||||
using namespace std; | |||||
/* static */ | |||||
CvTransformation CvScaler::transformations_[ADC_CHANNEL_LAST] = { | |||||
// ADC_POSITION_POTENTIOMETER_CV, | |||||
{ true, false, 0.05f }, | |||||
// ADC_DENSITY_POTENTIOMETER_CV, | |||||
{ true, false, 0.01f }, | |||||
// ADC_SIZE_POTENTIOMETER, | |||||
{ false, false, 0.01f }, | |||||
// ADC_SIZE_CV, | |||||
{ false, true, 0.1f }, | |||||
// ADC_PITCH_POTENTIOMETER, | |||||
{ false, false, 0.01f }, | |||||
// ADC_V_OCT_CV, | |||||
{ false, false, 1.00f }, | |||||
// ADC_BLEND_POTENTIOMETER, | |||||
{ false, false, 0.05f }, | |||||
// ADC_BLEND_CV, | |||||
{ false, true, 0.2f }, | |||||
// ADC_TEXTURE_POTENTIOMETER, | |||||
{ false, false, 0.01f }, | |||||
// ADC_TEXTURE_CV, | |||||
{ false, true, 0.01f } | |||||
}; | |||||
void CvScaler::Init(CalibrationData* calibration_data) { | |||||
adc_.Init(); | |||||
gate_input_.Init(); | |||||
calibration_data_ = calibration_data; | |||||
fill(&smoothed_adc_value_[0], &smoothed_adc_value_[ADC_CHANNEL_LAST], 0.0f); | |||||
note_ = 0.0f; | |||||
fill(&blend_[0], &blend_[BLEND_PARAMETER_LAST], 0.0f); | |||||
fill(&blend_mod_[0], &blend_mod_[BLEND_PARAMETER_LAST], 0.0f); | |||||
previous_blend_knob_value_ = 0.0f; | |||||
blend_parameter_ = BLEND_PARAMETER_DRY_WET; | |||||
blend_knob_quantized_ = -1.0f; | |||||
blend_knob_touched_ = false; | |||||
fill(&previous_trigger_[0], &previous_trigger_[kAdcLatency], false); | |||||
fill(&previous_gate_[0], &previous_gate_[kAdcLatency], false); | |||||
} | |||||
void CvScaler::UpdateBlendParameters(float knob_value, float cv) { | |||||
// Update the blending settings (base value and modulation) from the | |||||
// Blend knob and CV. | |||||
for (int32_t i = 0; i < BLEND_PARAMETER_LAST; ++i) { | |||||
float target = i == blend_parameter_ ? cv : 0.0f; | |||||
float coefficient = i == blend_parameter_ ? 0.1f : 0.002f; | |||||
blend_mod_[i] += coefficient * (target - blend_mod_[i]); | |||||
} | |||||
// Determines if the blend knob has been touched. | |||||
if (blend_knob_quantized_ == -1.0f) { | |||||
blend_knob_quantized_ = knob_value; | |||||
} | |||||
blend_knob_touched_ = fabs(knob_value - blend_knob_quantized_) > 0.02f; | |||||
if (blend_knob_touched_) { | |||||
blend_knob_quantized_ = knob_value; | |||||
} | |||||
if (previous_blend_knob_value_ == -1.0f) { | |||||
blend_[blend_parameter_] = knob_value; | |||||
previous_blend_knob_value_ = knob_value; | |||||
blend_knob_origin_ = knob_value; | |||||
} | |||||
float parameter_value = blend_[blend_parameter_]; | |||||
float delta = knob_value - previous_blend_knob_value_; | |||||
float skew_ratio = delta > 0.0f | |||||
? (1.001f - parameter_value) / (1.001f - previous_blend_knob_value_) | |||||
: (0.001f + parameter_value) / (0.001f + previous_blend_knob_value_); | |||||
CONSTRAIN(skew_ratio, 0.1f, 10.0f); | |||||
if (fabs(knob_value - blend_knob_origin_) < 0.02f) { | |||||
delta = 0.0f; | |||||
} else { | |||||
blend_knob_origin_ = -1.0f; | |||||
} | |||||
parameter_value += skew_ratio * delta; | |||||
CONSTRAIN(parameter_value, 0.0f, 1.0f); | |||||
blend_[blend_parameter_] = parameter_value; | |||||
previous_blend_knob_value_ = knob_value; | |||||
} | |||||
void CvScaler::Read(Parameters* parameters) { | |||||
for (size_t i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||||
const CvTransformation& transformation = transformations_[i]; | |||||
float value = adc_.float_value(i); | |||||
if (transformation.flip) { | |||||
value = 1.0f - value; | |||||
} | |||||
if (transformation.remove_offset) { | |||||
value -= calibration_data_->offset[i]; | |||||
} | |||||
smoothed_adc_value_[i] += transformation.filter_coefficient * \ | |||||
(value - smoothed_adc_value_[i]); | |||||
} | |||||
parameters->position = smoothed_adc_value_[ADC_POSITION_POTENTIOMETER_CV]; | |||||
float texture = smoothed_adc_value_[ADC_TEXTURE_POTENTIOMETER]; | |||||
texture -= smoothed_adc_value_[ADC_TEXTURE_CV] * 2.0f; | |||||
CONSTRAIN(texture, 0.0f, 1.0f); | |||||
parameters->texture = texture; | |||||
float density = smoothed_adc_value_[ADC_DENSITY_POTENTIOMETER_CV]; | |||||
CONSTRAIN(density, 0.0f, 1.0f); | |||||
parameters->density = density; | |||||
parameters->size = smoothed_adc_value_[ADC_SIZE_POTENTIOMETER]; | |||||
parameters->size -= smoothed_adc_value_[ADC_SIZE_CV]; | |||||
CONSTRAIN(parameters->size, 0.0f, 1.0f); | |||||
UpdateBlendParameters( | |||||
smoothed_adc_value_[ADC_BLEND_POTENTIOMETER], | |||||
-smoothed_adc_value_[ADC_BLEND_CV] * 2.0f); | |||||
float dry_wet = blend_[BLEND_PARAMETER_DRY_WET]; | |||||
dry_wet += blend_mod_[BLEND_PARAMETER_DRY_WET]; | |||||
dry_wet = dry_wet * 1.05f - 0.025f; | |||||
CONSTRAIN(dry_wet, 0.0f, 1.0f); | |||||
parameters->dry_wet = dry_wet; | |||||
float reverb_amount = blend_[BLEND_PARAMETER_REVERB]; | |||||
reverb_amount += blend_mod_[BLEND_PARAMETER_REVERB]; | |||||
CONSTRAIN(reverb_amount, 0.0f, 1.0f); | |||||
parameters->reverb = reverb_amount; | |||||
float feedback = blend_[BLEND_PARAMETER_FEEDBACK]; | |||||
feedback += blend_mod_[BLEND_PARAMETER_FEEDBACK]; | |||||
CONSTRAIN(feedback, 0.0f, 1.0f); | |||||
parameters->feedback = feedback; | |||||
float stereo_spread = blend_[BLEND_PARAMETER_STEREO_SPREAD]; | |||||
stereo_spread += blend_mod_[BLEND_PARAMETER_STEREO_SPREAD]; | |||||
CONSTRAIN(stereo_spread, 0.0f, 1.0f); | |||||
parameters->stereo_spread = stereo_spread; | |||||
parameters->pitch = stmlib::Interpolate( | |||||
lut_quantized_pitch, | |||||
smoothed_adc_value_[ADC_PITCH_POTENTIOMETER], | |||||
1024.0f); | |||||
float note = calibration_data_->pitch_offset; | |||||
note += smoothed_adc_value_[ADC_V_OCT_CV] * calibration_data_->pitch_scale; | |||||
if (fabs(note - note_) > 0.5f) { | |||||
note_ = note; | |||||
} else { | |||||
ONE_POLE(note_, note, 0.2f) | |||||
} | |||||
parameters->pitch += note_; | |||||
CONSTRAIN(parameters->pitch, -48.0f, 48.0f); | |||||
gate_input_.Read(); | |||||
if (gate_input_.freeze_rising_edge()) { | |||||
parameters->freeze = true; | |||||
} else if (gate_input_.freeze_falling_edge()) { | |||||
parameters->freeze = false; | |||||
} | |||||
parameters->trigger = previous_trigger_[0]; | |||||
parameters->gate = previous_gate_[0]; | |||||
for (int i = 0; i < kAdcLatency - 1; ++i) { | |||||
previous_trigger_[i] = previous_trigger_[i + 1]; | |||||
previous_gate_[i] = previous_gate_[i + 1]; | |||||
} | |||||
previous_trigger_[kAdcLatency - 1] = gate_input_.trigger_rising_edge(); | |||||
previous_gate_[kAdcLatency - 1] = gate_input_.gate(); | |||||
adc_.Convert(); | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,156 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Calibration settings. | |||||
#ifndef CLOUDS_CV_SCALER_H_ | |||||
#define CLOUDS_CV_SCALER_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "clouds/settings.h" | |||||
#include "clouds/drivers/adc.h" | |||||
#include "clouds/drivers/gate_input.h" | |||||
#include "clouds/dsp/parameters.h" | |||||
namespace clouds { | |||||
enum BlendParameter { | |||||
BLEND_PARAMETER_DRY_WET, | |||||
BLEND_PARAMETER_STEREO_SPREAD, | |||||
BLEND_PARAMETER_FEEDBACK, | |||||
BLEND_PARAMETER_REVERB, | |||||
BLEND_PARAMETER_LAST | |||||
}; | |||||
struct CvTransformation { | |||||
bool flip; | |||||
bool remove_offset; | |||||
float filter_coefficient; | |||||
}; | |||||
class CvScaler { | |||||
public: | |||||
CvScaler() { } | |||||
~CvScaler() { } | |||||
void Init(CalibrationData* calibration_data); | |||||
void Read(Parameters* parameters); | |||||
void CalibrateC1() { | |||||
cv_c1_ = adc_.float_value(ADC_V_OCT_CV); | |||||
} | |||||
void CalibrateOffsets() { | |||||
for (size_t i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||||
calibration_data_->offset[i] = adc_.float_value(i); | |||||
} | |||||
} | |||||
bool CalibrateC3() { | |||||
float c3 = adc_.float_value(ADC_V_OCT_CV); // 0.4848 v0.1 ; 0.3640 v0.2 | |||||
float c1 = cv_c1_; // 0.6666 v0.1 ; 0.6488 v0.2 | |||||
float delta = c3 - c1; | |||||
if (delta > -0.5f && delta < -0.0f) { | |||||
calibration_data_->pitch_scale = 24.0f / (c3 - c1); | |||||
calibration_data_->pitch_offset = 12.0f - \ | |||||
calibration_data_->pitch_scale * c1; | |||||
return true; | |||||
} else { | |||||
return false; | |||||
} | |||||
} | |||||
inline uint8_t adc_value(size_t index) const { | |||||
return adc_.value(index) >> 8; | |||||
} | |||||
inline bool gate(size_t index) const { | |||||
return index == 0 ? gate_input_.freeze() : gate_input_.trigger(); | |||||
} | |||||
inline void set_blend_parameter(BlendParameter parameter) { | |||||
blend_parameter_ = parameter; | |||||
blend_knob_origin_ = previous_blend_knob_value_; | |||||
} | |||||
inline void MatchKnobPosition() { | |||||
previous_blend_knob_value_ = -1.0f; | |||||
} | |||||
inline BlendParameter blend_parameter() const { | |||||
return blend_parameter_; | |||||
} | |||||
inline float blend_value(BlendParameter parameter) const { | |||||
return blend_[parameter]; | |||||
} | |||||
inline void set_blend_value(BlendParameter parameter, float value) { | |||||
blend_[parameter] = value; | |||||
} | |||||
inline bool blend_knob_touched() const { | |||||
return blend_knob_touched_; | |||||
} | |||||
void UnlockBlendKnob() { | |||||
previous_blend_knob_value_ = -1.0f; | |||||
} | |||||
private: | |||||
void UpdateBlendParameters(float knob, float cv); | |||||
static const int kAdcLatency = 5; | |||||
Adc adc_; | |||||
GateInput gate_input_; | |||||
CalibrationData* calibration_data_; | |||||
float smoothed_adc_value_[ADC_CHANNEL_LAST]; | |||||
static CvTransformation transformations_[ADC_CHANNEL_LAST]; | |||||
float note_; | |||||
BlendParameter blend_parameter_; | |||||
float blend_[BLEND_PARAMETER_LAST]; | |||||
float blend_mod_[BLEND_PARAMETER_LAST]; | |||||
float previous_blend_knob_value_; | |||||
float blend_knob_origin_; | |||||
float blend_knob_quantized_; | |||||
bool blend_knob_touched_; | |||||
float cv_c1_; | |||||
bool previous_trigger_[kAdcLatency]; | |||||
bool previous_gate_[kAdcLatency]; | |||||
DISALLOW_COPY_AND_ASSIGN(CvScaler); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_CV_SCALER_H_ |
@@ -0,0 +1,116 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for ADC. | |||||
#include "clouds/drivers/adc.h" | |||||
#include <stm32f4xx_conf.h> | |||||
namespace clouds { | |||||
void Adc::Init() { | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOC, ENABLE); | |||||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); | |||||
DMA_InitTypeDef dma_init; | |||||
ADC_CommonInitTypeDef adc_common_init; | |||||
ADC_InitTypeDef adc_init; | |||||
GPIO_InitTypeDef gpio_init; | |||||
gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; | |||||
gpio_init.GPIO_Pin |= GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||||
GPIO_Init(GPIOA, &gpio_init); | |||||
gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||||
GPIO_Init(GPIOC, &gpio_init); | |||||
// Use DMA to automatically copy ADC data register to values_ buffer. | |||||
dma_init.DMA_Channel = DMA_Channel_0; | |||||
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; | |||||
dma_init.DMA_Memory0BaseAddr = (uint32_t)&values_[0]; | |||||
dma_init.DMA_DIR = DMA_DIR_PeripheralToMemory; | |||||
dma_init.DMA_BufferSize = ADC_CHANNEL_LAST; | |||||
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||||
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||||
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||||
dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||||
dma_init.DMA_Mode = DMA_Mode_Circular; | |||||
dma_init.DMA_Priority = DMA_Priority_High; | |||||
dma_init.DMA_FIFOMode = DMA_FIFOMode_Disable; | |||||
dma_init.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; | |||||
dma_init.DMA_MemoryBurst = DMA_MemoryBurst_Single; | |||||
dma_init.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; | |||||
DMA_Init(DMA2_Stream0, &dma_init); | |||||
DMA_Cmd(DMA2_Stream0, ENABLE); | |||||
adc_common_init.ADC_Mode = ADC_Mode_Independent; | |||||
adc_common_init.ADC_Prescaler = ADC_Prescaler_Div8; | |||||
adc_common_init.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; | |||||
adc_common_init.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles; | |||||
ADC_CommonInit(&adc_common_init); | |||||
adc_init.ADC_Resolution = ADC_Resolution_12b; | |||||
adc_init.ADC_ScanConvMode = ENABLE; | |||||
adc_init.ADC_ContinuousConvMode = DISABLE; | |||||
adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; | |||||
adc_init.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; | |||||
adc_init.ADC_DataAlign = ADC_DataAlign_Left; | |||||
adc_init.ADC_NbrOfConversion = ADC_CHANNEL_LAST; | |||||
ADC_Init(ADC1, &adc_init); | |||||
// 168M / 2 / 8 / (10 x (480 + 20)) = 2.1kHz. | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_480Cycles); | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_480Cycles); | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 3, ADC_SampleTime_480Cycles); | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 4, ADC_SampleTime_480Cycles); | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 5, ADC_SampleTime_480Cycles); | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 6, ADC_SampleTime_480Cycles); | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 7, ADC_SampleTime_480Cycles); | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 8, ADC_SampleTime_480Cycles); | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 9, ADC_SampleTime_480Cycles); | |||||
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 10, ADC_SampleTime_480Cycles); | |||||
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE); | |||||
ADC_Cmd(ADC1, ENABLE); | |||||
ADC_DMACmd(ADC1, ENABLE); | |||||
Convert(); | |||||
} | |||||
void Adc::Convert() { | |||||
ADC_SoftwareStartConv(ADC1); | |||||
} | |||||
void Adc::DeInit() { | |||||
ADC_DeInit(); | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,72 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for ADC. | |||||
#ifndef CLOUDS_DRIVERS_ADC_H_ | |||||
#define CLOUDS_DRIVERS_ADC_H_ | |||||
#include "stmlib/stmlib.h" | |||||
namespace clouds { | |||||
enum AdcChannel { | |||||
ADC_POSITION_POTENTIOMETER_CV, | |||||
ADC_DENSITY_POTENTIOMETER_CV, | |||||
ADC_SIZE_POTENTIOMETER, | |||||
ADC_SIZE_CV, | |||||
ADC_PITCH_POTENTIOMETER, | |||||
ADC_V_OCT_CV, | |||||
ADC_BLEND_POTENTIOMETER, | |||||
ADC_BLEND_CV, | |||||
ADC_TEXTURE_POTENTIOMETER, | |||||
ADC_TEXTURE_CV, | |||||
ADC_CHANNEL_LAST | |||||
}; | |||||
class Adc { | |||||
public: | |||||
Adc() { } | |||||
~Adc() { } | |||||
void Init(); | |||||
void DeInit(); | |||||
void Convert(); | |||||
inline uint16_t value(uint8_t channel) const { return values_[channel]; } | |||||
inline float float_value(uint8_t channel) const { | |||||
return static_cast<float>(values_[channel]) / 65536.0f; | |||||
} | |||||
inline const uint16_t* values() { return &values_[0]; } | |||||
private: | |||||
uint16_t values_[ADC_CHANNEL_LAST]; | |||||
DISALLOW_COPY_AND_ASSIGN(Adc); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DRIVERS_ADC_H_ |
@@ -0,0 +1,564 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// WM8371 Codec support. | |||||
#include "clouds/drivers/codec.h" | |||||
#include <string.h> | |||||
#define CODEC_I2C I2C2 | |||||
#define CODEC_I2C_CLK RCC_APB1Periph_I2C2 | |||||
#define CODEC_I2C_GPIO_CLOCK RCC_AHB1Periph_GPIOB | |||||
#define CODEC_I2C_GPIO_AF GPIO_AF_I2C2 | |||||
#define CODEC_I2C_GPIO GPIOB | |||||
#define CODEC_I2C_SCL_PIN GPIO_Pin_10 | |||||
#define CODEC_I2C_SDA_PIN GPIO_Pin_11 | |||||
#define CODEC_I2C_SCL_PINSRC GPIO_PinSource10 | |||||
#define CODEC_I2C_SDA_PINSRC GPIO_PinSource11 | |||||
#define CODEC_TIMEOUT ((uint32_t)0x1000) | |||||
#define CODEC_LONG_TIMEOUT ((uint32_t)(300 * CODEC_TIMEOUT)) | |||||
#define CODEC_I2C_SPEED 100000 | |||||
#define CODEC_I2S SPI2 | |||||
#define CODEC_I2S_EXT I2S2ext | |||||
#define CODEC_I2S_CLK RCC_APB1Periph_SPI2 | |||||
#define CODEC_I2S_ADDRESS 0x4000380C | |||||
#define CODEC_I2S_EXT_ADDRESS 0x4000340C | |||||
#define CODEC_I2S_GPIO_AF GPIO_AF_SPI2 | |||||
#define CODEC_I2S_IRQ SPI2_IRQn | |||||
#define CODEC_I2S_EXT_IRQ SPI2_IRQn | |||||
#define CODEC_I2S_GPIO_CLOCK (RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOB) | |||||
#define CODEC_I2S_WS_PIN GPIO_Pin_12 | |||||
#define CODEC_I2S_SCK_PIN GPIO_Pin_13 | |||||
#define CODEC_I2S_SDI_PIN GPIO_Pin_14 | |||||
#define CODEC_I2S_SDO_PIN GPIO_Pin_15 | |||||
#define CODEC_I2S_MCK_PIN GPIO_Pin_6 | |||||
#define CODEC_I2S_WS_PINSRC GPIO_PinSource12 | |||||
#define CODEC_I2S_SCK_PINSRC GPIO_PinSource13 | |||||
#define CODEC_I2S_SDI_PINSRC GPIO_PinSource14 | |||||
#define CODEC_I2S_SDO_PINSRC GPIO_PinSource15 | |||||
#define CODEC_I2S_MCK_PINSRC GPIO_PinSource6 | |||||
#define CODEC_I2S_GPIO GPIOB | |||||
#define CODEC_I2S_MCK_GPIO GPIOC | |||||
#define AUDIO_I2S_IRQHandler SPI2_IRQHandler | |||||
#define AUDIO_DMA_PERIPH_DATA_SIZE DMA_PeripheralDataSize_HalfWord | |||||
#define AUDIO_DMA_MEM_DATA_SIZE DMA_MemoryDataSize_HalfWord | |||||
#define AUDIO_I2S_DMA_CLOCK RCC_AHB1Periph_DMA1 | |||||
#define AUDIO_I2S_DMA_STREAM DMA1_Stream4 | |||||
#define AUDIO_I2S_DMA_DREG CODEC_I2S_ADDRESS | |||||
#define AUDIO_I2S_DMA_CHANNEL DMA_Channel_0 | |||||
#define AUDIO_I2S_DMA_IRQ DMA1_Stream4_IRQn | |||||
#define AUDIO_I2S_DMA_FLAG_TC DMA_FLAG_TCIF4 | |||||
#define AUDIO_I2S_DMA_FLAG_HT DMA_FLAG_HTIF4 | |||||
#define AUDIO_I2S_DMA_FLAG_FE DMA_FLAG_FEIF4 | |||||
#define AUDIO_I2S_DMA_FLAG_TE DMA_FLAG_TEIF4 | |||||
#define AUDIO_I2S_DMA_FLAG_DME DMA_FLAG_DMEIF4 | |||||
#define AUDIO_I2S_EXT_DMA_STREAM DMA1_Stream3 | |||||
#define AUDIO_I2S_EXT_DMA_DREG CODEC_I2S_EXT_ADDRESS | |||||
#define AUDIO_I2S_EXT_DMA_CHANNEL DMA_Channel_3 | |||||
#define AUDIO_I2S_EXT_DMA_IRQ DMA1_Stream3_IRQn | |||||
#define AUDIO_I2S_EXT_DMA_FLAG_TC DMA_FLAG_TCIF3 | |||||
#define AUDIO_I2S_EXT_DMA_FLAG_HT DMA_FLAG_HTIF3 | |||||
#define AUDIO_I2S_EXT_DMA_FLAG_FE DMA_FLAG_FEIF3 | |||||
#define AUDIO_I2S_EXT_DMA_FLAG_TE DMA_FLAG_TEIF3 | |||||
#define AUDIO_I2S_EXT_DMA_FLAG_DME DMA_FLAG_DMEIF3 | |||||
#define AUDIO_I2S_EXT_DMA_REG DMA1 | |||||
#define AUDIO_I2S_EXT_DMA_ISR LISR | |||||
#define AUDIO_I2S_EXT_DMA_IFCR LIFCR | |||||
#define W8731_ADDR_0 0x1A | |||||
#define W8731_ADDR_1 0x1B | |||||
#define W8731_NUM_REGS 10 | |||||
#define CODEC_ADDRESS (W8731_ADDR_0 << 1) | |||||
#define WAIT_LONG(x) { \ | |||||
uint32_t timeout = CODEC_LONG_TIMEOUT; \ | |||||
while (x) { if ((timeout--) == 0) return false; } \ | |||||
} | |||||
#define WAIT(x) { \ | |||||
uint32_t timeout = CODEC_TIMEOUT; \ | |||||
while (x) { if ((timeout--) == 0) return false; } \ | |||||
} | |||||
namespace clouds { | |||||
/* static */ | |||||
Codec* Codec::instance_; | |||||
enum CodecRegister { | |||||
CODEC_REG_LEFT_LINE_IN = 0x00, | |||||
CODEC_REG_RIGHT_LINE_IN = 0x01, | |||||
CODEC_REG_LEFT_HEADPHONES_OUT = 0x02, | |||||
CODEC_REG_RIGHT_HEADPHONES_OUT = 0x03, | |||||
CODEC_REG_ANALOGUE_ROUTING = 0x04, | |||||
CODEC_REG_DIGITAL_ROUTING = 0x05, | |||||
CODEC_REG_POWER_MANAGEMENT = 0x06, | |||||
CODEC_REG_DIGITAL_FORMAT = 0x07, | |||||
CODEC_REG_SAMPLE_RATE = 0x08, | |||||
CODEC_REG_ACTIVE = 0x09, | |||||
CODEC_REG_RESET = 0x0f, | |||||
}; | |||||
enum CodecSettings { | |||||
CODEC_INPUT_0_DB = 0x17, | |||||
CODEC_INPUT_UPDATE_BOTH = 0x40, | |||||
CODEC_HEADPHONES_MUTE = 0x00, | |||||
CODEC_MIC_BOOST = 0x1, | |||||
CODEC_MIC_MUTE = 0x2, | |||||
CODEC_ADC_MIC = 0x4, | |||||
CODEC_ADC_LINE = 0x0, | |||||
CODEC_OUTPUT_DAC_ENABLE = 0x10, | |||||
CODEC_OUTPUT_MONITOR = 0x20, | |||||
CODEC_DEEMPHASIS_NONE = 0x00, | |||||
CODEC_DEEMPHASIS_32K = 0x01, | |||||
CODEC_DEEMPHASIS_44K = 0x02, | |||||
CODEC_DEEMPHASIS_48K = 0x03, | |||||
CODEC_SOFT_MUTE = 0x01, | |||||
CODEC_ADC_HPF = 0x00, | |||||
CODEC_POWER_DOWN_LINE_IN = 0x01, | |||||
CODEC_POWER_DOWN_MIC = 0x02, | |||||
CODEC_POWER_DOWN_ADC = 0x04, | |||||
CODEC_POWER_DOWN_DAC = 0x08, | |||||
CODEC_POWER_DOWN_LINE_OUT = 0x10, | |||||
CODEC_POWER_DOWN_OSCILLATOR = 0x20, | |||||
CODEC_POWER_DOWN_CLOCK_OUTPUT = 0x40, | |||||
CODEC_POWER_DOWN_EVERYTHING = 0x80, | |||||
CODEC_PROTOCOL_MASK_MSB_FIRST = 0x00, | |||||
CODEC_PROTOCOL_MASK_LSB_FIRST = 0x01, | |||||
CODEC_PROTOCOL_MASK_PHILIPS = 0x02, | |||||
CODEC_PROTOCOL_MASK_DSP = 0x03, | |||||
CODEC_FORMAT_MASK_16_BIT = 0x00 << 2, | |||||
CODEC_FORMAT_MASK_20_BIT = 0x01 << 2, | |||||
CODEC_FORMAT_MASK_24_BIT = 0x02 << 2, | |||||
CODEC_FORMAT_MASK_32_BIT = 0x03 << 2, | |||||
CODEC_FORMAT_LR_SWAP = 0x20, | |||||
CODEC_FORMAT_MASTER = 0x40, | |||||
CODEC_FORMAT_SLAVE = 0x00, | |||||
CODEC_FORMAT_INVERT_CLOCK = 0x80, | |||||
CODEC_RATE_48K_48K = 0x00 << 2, | |||||
CODEC_RATE_8K_8K = 0x03 << 2, | |||||
CODEC_RATE_96K_96K = 0x07 << 2, | |||||
CODEC_RATE_32K_32K = 0x06 << 2, | |||||
CODEC_RATE_44K_44K = 0x08 << 2, | |||||
}; | |||||
bool Codec::InitializeGPIO() { | |||||
GPIO_InitTypeDef gpio_init; | |||||
// Start GPIO peripheral clocks. | |||||
RCC_AHB1PeriphClockCmd(CODEC_I2C_GPIO_CLOCK | CODEC_I2S_GPIO_CLOCK, ENABLE); | |||||
// Initialize I2C pins | |||||
gpio_init.GPIO_Pin = CODEC_I2C_SCL_PIN | CODEC_I2C_SDA_PIN; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_50MHz; | |||||
gpio_init.GPIO_OType = GPIO_OType_OD; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||||
GPIO_Init(CODEC_I2C_GPIO, &gpio_init); | |||||
// Connect pins to I2C peripheral | |||||
GPIO_PinAFConfig(CODEC_I2C_GPIO, CODEC_I2C_SCL_PINSRC, CODEC_I2C_GPIO_AF); | |||||
GPIO_PinAFConfig(CODEC_I2C_GPIO, CODEC_I2C_SDA_PINSRC, CODEC_I2C_GPIO_AF); | |||||
// Initialize I2S pins | |||||
gpio_init.GPIO_Pin = CODEC_I2S_SCK_PIN | CODEC_I2S_SDO_PIN | \ | |||||
CODEC_I2S_SDI_PIN | CODEC_I2S_WS_PIN; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_50MHz; | |||||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||||
GPIO_Init(CODEC_I2S_GPIO, &gpio_init); | |||||
gpio_init.GPIO_Pin = CODEC_I2S_MCK_PIN; | |||||
GPIO_Init(CODEC_I2S_MCK_GPIO, &gpio_init); | |||||
// Connect pins to I2S peripheral. | |||||
GPIO_PinAFConfig(CODEC_I2S_GPIO, CODEC_I2S_WS_PINSRC, CODEC_I2S_GPIO_AF); | |||||
GPIO_PinAFConfig(CODEC_I2S_GPIO, CODEC_I2S_SCK_PINSRC, CODEC_I2S_GPIO_AF); | |||||
GPIO_PinAFConfig(CODEC_I2S_GPIO, CODEC_I2S_SDO_PINSRC, CODEC_I2S_GPIO_AF); | |||||
GPIO_PinAFConfig(CODEC_I2S_GPIO, CODEC_I2S_SDI_PINSRC, CODEC_I2S_GPIO_AF); | |||||
GPIO_PinAFConfig(CODEC_I2S_MCK_GPIO, CODEC_I2S_MCK_PINSRC, CODEC_I2S_GPIO_AF); | |||||
return true; | |||||
} | |||||
bool Codec::InitializeControlInterface() { | |||||
I2C_InitTypeDef i2c_init; | |||||
// Initialize I2C | |||||
RCC_APB1PeriphClockCmd(CODEC_I2C_CLK, ENABLE); | |||||
I2C_DeInit(CODEC_I2C); | |||||
i2c_init.I2C_Mode = I2C_Mode_I2C; | |||||
i2c_init.I2C_DutyCycle = I2C_DutyCycle_2; | |||||
i2c_init.I2C_OwnAddress1 = 0x33; | |||||
i2c_init.I2C_Ack = I2C_Ack_Enable; | |||||
i2c_init.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; | |||||
i2c_init.I2C_ClockSpeed = CODEC_I2C_SPEED; | |||||
I2C_Init(CODEC_I2C, &i2c_init); | |||||
I2C_Cmd(CODEC_I2C, ENABLE); | |||||
return true; | |||||
} | |||||
bool Codec::InitializeAudioInterface( | |||||
bool mcu_is_master, | |||||
int32_t sample_rate) { | |||||
// Configure PLL and I2S master clock. | |||||
RCC_I2SCLKConfig(RCC_I2S2CLKSource_PLLI2S); | |||||
// The following values have been computed for a 8Mhz external crystal! | |||||
RCC_PLLI2SCmd(DISABLE); | |||||
if (sample_rate == 48000) { | |||||
// 47.992kHz | |||||
RCC_PLLI2SConfig(258, 3); | |||||
} else if (sample_rate == 44100) { | |||||
// 44.11kHz | |||||
RCC_PLLI2SConfig(271, 6); | |||||
} else if (sample_rate == 32000) { | |||||
// 32.003kHz | |||||
RCC_PLLI2SConfig(426, 4); | |||||
} else if (sample_rate == 96000) { | |||||
// 95.95 kHz | |||||
RCC_PLLI2SConfig(393, 4); | |||||
} else { | |||||
// Unsupported sample rate! | |||||
return false; | |||||
} | |||||
RCC_PLLI2SCmd(ENABLE); | |||||
WAIT(RCC_GetFlagStatus(RCC_FLAG_PLLI2SRDY) == RESET); | |||||
RCC_APB1PeriphClockCmd(CODEC_I2S_CLK, ENABLE); | |||||
// Initialize I2S | |||||
I2S_InitTypeDef i2s_init; | |||||
SPI_I2S_DeInit(CODEC_I2S); | |||||
i2s_init.I2S_AudioFreq = sample_rate; | |||||
i2s_init.I2S_Standard = I2S_Standard_Phillips; | |||||
i2s_init.I2S_DataFormat = I2S_DataFormat_16b; | |||||
i2s_init.I2S_CPOL = I2S_CPOL_Low; | |||||
i2s_init.I2S_Mode = mcu_is_master ? I2S_Mode_MasterTx : I2S_Mode_SlaveTx; | |||||
i2s_init.I2S_MCLKOutput = mcu_is_master | |||||
? I2S_MCLKOutput_Enable | |||||
: I2S_MCLKOutput_Disable; | |||||
// Initialize the I2S main channel for TX | |||||
I2S_Init(CODEC_I2S, &i2s_init); | |||||
// Initialize the I2S extended channel for RX | |||||
I2S_FullDuplexConfig(CODEC_I2S_EXT, &i2s_init); | |||||
return true; | |||||
} | |||||
bool Codec::WriteControlRegister(uint8_t address, uint16_t data) { | |||||
uint8_t byte_1 = ((address << 1) & 0xfe) | ((data >> 8) & 0x01); | |||||
uint8_t byte_2 = data & 0xff; | |||||
WAIT_LONG(I2C_GetFlagStatus(CODEC_I2C, I2C_FLAG_BUSY)); | |||||
I2C_GenerateSTART(CODEC_I2C, ENABLE); | |||||
WAIT(!I2C_CheckEvent(CODEC_I2C, I2C_EVENT_MASTER_MODE_SELECT)); | |||||
I2C_Send7bitAddress(CODEC_I2C, CODEC_ADDRESS, I2C_Direction_Transmitter); | |||||
WAIT(!I2C_CheckEvent(CODEC_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); | |||||
I2C_SendData(CODEC_I2C, byte_1); | |||||
WAIT(!I2C_CheckEvent(CODEC_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); | |||||
I2C_SendData(CODEC_I2C, byte_2); | |||||
WAIT(!I2C_CheckEvent(CODEC_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); | |||||
WAIT_LONG(!I2C_GetFlagStatus(CODEC_I2C, I2C_FLAG_BTF)); | |||||
I2C_GenerateSTOP(CODEC_I2C, ENABLE); | |||||
return true; | |||||
} | |||||
bool Codec::InitializeCodec( | |||||
bool mcu_is_master, | |||||
int32_t sample_rate) { | |||||
bool s = true; // success; | |||||
s = s && WriteControlRegister(CODEC_REG_RESET, 0); | |||||
// Configure L&R inputs | |||||
s = s && WriteControlRegister(CODEC_REG_LEFT_LINE_IN, CODEC_INPUT_0_DB); | |||||
s = s && WriteControlRegister(CODEC_REG_RIGHT_LINE_IN, CODEC_INPUT_0_DB); | |||||
// Configure L&R headphone outputs | |||||
s = s && WriteControlRegister(CODEC_REG_LEFT_HEADPHONES_OUT, CODEC_HEADPHONES_MUTE); | |||||
s = s && WriteControlRegister(CODEC_REG_RIGHT_HEADPHONES_OUT, CODEC_HEADPHONES_MUTE); | |||||
// Configure analog routing | |||||
s = s && WriteControlRegister( | |||||
CODEC_REG_ANALOGUE_ROUTING, | |||||
CODEC_MIC_MUTE | CODEC_ADC_LINE | CODEC_OUTPUT_DAC_ENABLE); | |||||
// Configure digital routing | |||||
s = s && WriteControlRegister(CODEC_REG_DIGITAL_ROUTING, CODEC_DEEMPHASIS_NONE); | |||||
// Configure power management | |||||
uint8_t power_down_reg = CODEC_POWER_DOWN_MIC | CODEC_POWER_DOWN_CLOCK_OUTPUT; | |||||
if (mcu_is_master) { | |||||
power_down_reg |= CODEC_POWER_DOWN_OSCILLATOR; | |||||
} | |||||
s = s && WriteControlRegister(CODEC_REG_POWER_MANAGEMENT, power_down_reg); | |||||
uint8_t format_byte = CODEC_PROTOCOL_MASK_PHILIPS | CODEC_FORMAT_MASK_16_BIT; | |||||
format_byte |= mcu_is_master ? CODEC_FORMAT_SLAVE : CODEC_FORMAT_MASTER; | |||||
s = s && WriteControlRegister(CODEC_REG_DIGITAL_FORMAT, format_byte); | |||||
uint8_t rate_byte = 0; | |||||
if (mcu_is_master) { | |||||
// According to the WM8731 datasheet, the 32kHz and 96kHz modes require the | |||||
// master clock to be at 12.288 MHz (384 fs / 128 fs). The STM32F4 I2S clock | |||||
// is always at 256 fs. So the 32kHz and 96kHz modes are achieved by | |||||
// pretending that we are doing 48kHz, but with a slower or faster master | |||||
// clock. | |||||
rate_byte = sample_rate == 44100 ? CODEC_RATE_44K_44K : CODEC_RATE_48K_48K; | |||||
} else { | |||||
switch (sample_rate) { | |||||
case 8000: | |||||
rate_byte = CODEC_RATE_8K_8K; | |||||
break; | |||||
case 32000: | |||||
rate_byte = CODEC_RATE_32K_32K; | |||||
break; | |||||
case 44100: | |||||
rate_byte = CODEC_RATE_44K_44K; | |||||
break; | |||||
case 96000: | |||||
rate_byte = CODEC_RATE_96K_96K; | |||||
break; | |||||
case 48000: | |||||
default: | |||||
rate_byte = CODEC_RATE_48K_48K; | |||||
break; | |||||
} | |||||
} | |||||
s = s && WriteControlRegister(CODEC_REG_SAMPLE_RATE, rate_byte); | |||||
// For now codec is not active. | |||||
s = s && WriteControlRegister(CODEC_REG_ACTIVE, 0x00); | |||||
return s; | |||||
} | |||||
bool Codec::InitializeDMA() { | |||||
RCC_AHB1PeriphClockCmd(AUDIO_I2S_DMA_CLOCK, ENABLE); | |||||
// DMA setup for TX. | |||||
DMA_Cmd(AUDIO_I2S_DMA_STREAM, DISABLE); | |||||
DMA_DeInit(AUDIO_I2S_DMA_STREAM); | |||||
dma_init_tx_.DMA_Channel = AUDIO_I2S_DMA_CHANNEL; | |||||
dma_init_tx_.DMA_PeripheralBaseAddr = AUDIO_I2S_DMA_DREG; | |||||
dma_init_tx_.DMA_Memory0BaseAddr = (uint32_t)0; | |||||
dma_init_tx_.DMA_DIR = DMA_DIR_MemoryToPeripheral; | |||||
dma_init_tx_.DMA_BufferSize = (uint32_t)0xFFFE; | |||||
dma_init_tx_.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||||
dma_init_tx_.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||||
dma_init_tx_.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||||
dma_init_tx_.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||||
dma_init_tx_.DMA_Mode = DMA_Mode_Circular; | |||||
dma_init_tx_.DMA_Priority = DMA_Priority_High; | |||||
dma_init_tx_.DMA_FIFOMode = DMA_FIFOMode_Disable; | |||||
dma_init_tx_.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; | |||||
dma_init_tx_.DMA_MemoryBurst = DMA_MemoryBurst_Single; | |||||
dma_init_tx_.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; | |||||
DMA_Init(AUDIO_I2S_DMA_STREAM, &dma_init_tx_); | |||||
// DMA setup for RX. | |||||
DMA_Cmd(AUDIO_I2S_EXT_DMA_STREAM, DISABLE); | |||||
DMA_DeInit(AUDIO_I2S_EXT_DMA_STREAM); | |||||
dma_init_rx_.DMA_Channel = AUDIO_I2S_EXT_DMA_CHANNEL; | |||||
dma_init_rx_.DMA_PeripheralBaseAddr = AUDIO_I2S_EXT_DMA_DREG; | |||||
dma_init_rx_.DMA_Memory0BaseAddr = (uint32_t)0; | |||||
dma_init_rx_.DMA_DIR = DMA_DIR_PeripheralToMemory; | |||||
dma_init_rx_.DMA_BufferSize = (uint32_t)0xFFFE; | |||||
dma_init_rx_.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||||
dma_init_rx_.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||||
dma_init_rx_.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||||
dma_init_rx_.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||||
dma_init_rx_.DMA_Mode = DMA_Mode_Circular; | |||||
dma_init_rx_.DMA_Priority = DMA_Priority_High; | |||||
dma_init_rx_.DMA_FIFOMode = DMA_FIFOMode_Disable; | |||||
dma_init_rx_.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; | |||||
dma_init_rx_.DMA_MemoryBurst = DMA_MemoryBurst_Single; | |||||
dma_init_rx_.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; | |||||
DMA_Init(AUDIO_I2S_EXT_DMA_STREAM, &dma_init_rx_); | |||||
// Enable the interrupts. | |||||
DMA_ITConfig(AUDIO_I2S_EXT_DMA_STREAM, DMA_IT_TC | DMA_IT_HT, ENABLE); | |||||
// Enable the IRQ. | |||||
NVIC_EnableIRQ(AUDIO_I2S_EXT_DMA_IRQ); | |||||
// Start DMA from/to codec. | |||||
SPI_I2S_DMACmd(CODEC_I2S, SPI_I2S_DMAReq_Tx, ENABLE); | |||||
SPI_I2S_DMACmd(CODEC_I2S_EXT, SPI_I2S_DMAReq_Rx, ENABLE); | |||||
return true; | |||||
} | |||||
bool Codec::Init( | |||||
bool mcu_is_master, | |||||
int32_t sample_rate) { | |||||
instance_ = this; | |||||
callback_ = NULL; | |||||
sample_rate_ = sample_rate; | |||||
mcu_is_master_ = mcu_is_master; | |||||
return InitializeGPIO() && \ | |||||
InitializeControlInterface() && \ | |||||
InitializeAudioInterface(mcu_is_master, sample_rate) && \ | |||||
InitializeCodec(mcu_is_master, sample_rate) && \ | |||||
InitializeDMA(); | |||||
} | |||||
bool Codec::Start(size_t block_size, FillBufferCallback callback) { | |||||
// Start the codec. | |||||
if (!WriteControlRegister(CODEC_REG_ACTIVE, 0x01)) { | |||||
return false; | |||||
} | |||||
if (block_size > kMaxCodecBlockSize) { | |||||
return false; | |||||
} | |||||
if (!mcu_is_master_) { | |||||
while(GPIO_ReadInputDataBit(CODEC_I2S_GPIO, CODEC_I2S_WS_PIN)); | |||||
while(!GPIO_ReadInputDataBit(CODEC_I2S_GPIO, CODEC_I2S_WS_PIN)); | |||||
} | |||||
callback_ = callback; | |||||
// Enable the I2S TX and RX peripherals. | |||||
if ((CODEC_I2S->I2SCFGR & 0x0400) == 0){ | |||||
I2S_Cmd(CODEC_I2S, ENABLE); | |||||
} | |||||
if ((CODEC_I2S_EXT->I2SCFGR & 0x0400) == 0){ | |||||
I2S_Cmd(CODEC_I2S_EXT, ENABLE); | |||||
} | |||||
dma_init_tx_.DMA_Memory0BaseAddr = (uint32_t)(tx_dma_buffer_); | |||||
dma_init_rx_.DMA_Memory0BaseAddr = (uint32_t)(rx_dma_buffer_); | |||||
size_t stride = 1; | |||||
if (!mcu_is_master_) { | |||||
// When the WM8731 is the master, the data is sent with padding. | |||||
switch (sample_rate_) { | |||||
case 32000: | |||||
stride = 3; | |||||
break; | |||||
case 48000: | |||||
stride = 2; | |||||
break; | |||||
case 96000: | |||||
stride = 4; | |||||
break; | |||||
} | |||||
} | |||||
block_size_ = block_size; | |||||
stride_ = stride; | |||||
dma_init_tx_.DMA_BufferSize = 2 * stride * block_size * 2; | |||||
dma_init_rx_.DMA_BufferSize = 2 * stride * block_size * 2; | |||||
DMA_Init(AUDIO_I2S_DMA_STREAM, &dma_init_tx_); | |||||
DMA_Init(AUDIO_I2S_EXT_DMA_STREAM, &dma_init_rx_); | |||||
DMA_Cmd(AUDIO_I2S_DMA_STREAM, ENABLE); | |||||
DMA_Cmd(AUDIO_I2S_EXT_DMA_STREAM, ENABLE); | |||||
return true; | |||||
} | |||||
void Codec::Stop() { | |||||
DMA_Cmd(AUDIO_I2S_DMA_STREAM, DISABLE); | |||||
DMA_Cmd(AUDIO_I2S_EXT_DMA_STREAM, DISABLE); | |||||
} | |||||
bool Codec::set_line_input_gain(int32_t channel, int32_t gain) { | |||||
return WriteControlRegister(CODEC_REG_LEFT_LINE_IN + channel, gain); | |||||
} | |||||
bool Codec::set_line_input_gain(int32_t gain) { | |||||
return WriteControlRegister(0, gain) && WriteControlRegister(1, gain); | |||||
} | |||||
void Codec::Fill(size_t offset) { | |||||
if (callback_) { | |||||
offset *= block_size_ * stride_ * 2; | |||||
short* in = &rx_dma_buffer_[offset]; | |||||
short* out = &tx_dma_buffer_[offset]; | |||||
if (stride_) { | |||||
// Undo the padding from the WM8731. | |||||
for (size_t i = 1; i < block_size_ * 2; ++i) { | |||||
in[i] = in[i * stride_]; | |||||
} | |||||
} | |||||
(*callback_)((Frame*)(in), (Frame*)(out), block_size_); | |||||
if (stride_) { | |||||
// Pad for the WM8731. | |||||
for (size_t i = block_size_ * 2 - 1; i > 0; --i) { | |||||
out[i * stride_] = out[i]; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} // namespace clouds | |||||
extern "C" { | |||||
// Do not call into the firmware library to save on calls/jumps. | |||||
// if (DMA_GetFlagStatus(AUDIO_I2S_EXT_DMA_STREAM, AUDIO_I2S_EXT_DMA_FLAG_TC) != RESET) { | |||||
// DMA_ClearFlag(AUDIO_I2S_EXT_DMA_STREAM, AUDIO_I2S_EXT_DMA_FLAG_TC); | |||||
void DMA1_Stream3_IRQHandler(void) { | |||||
if (AUDIO_I2S_EXT_DMA_REG->AUDIO_I2S_EXT_DMA_ISR & AUDIO_I2S_EXT_DMA_FLAG_TC) { | |||||
AUDIO_I2S_EXT_DMA_REG->AUDIO_I2S_EXT_DMA_IFCR = AUDIO_I2S_EXT_DMA_FLAG_TC; | |||||
clouds::Codec::GetInstance()->Fill(1); | |||||
} | |||||
if (AUDIO_I2S_EXT_DMA_REG->AUDIO_I2S_EXT_DMA_ISR & AUDIO_I2S_EXT_DMA_FLAG_HT) { | |||||
AUDIO_I2S_EXT_DMA_REG->AUDIO_I2S_EXT_DMA_IFCR = AUDIO_I2S_EXT_DMA_FLAG_HT; | |||||
clouds::Codec::GetInstance()->Fill(0); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,102 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// WM8371 Codec support. | |||||
#ifndef CLOUDS_DRIVERS_CODEC_H_ | |||||
#define CLOUDS_DRIVERS_CODEC_H_ | |||||
#include <stm32f4xx_conf.h> | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/utils/ring_buffer.h" | |||||
namespace clouds { | |||||
const size_t kMaxCodecBlockSize = 32; | |||||
class Codec { | |||||
public: | |||||
Codec() { } | |||||
~Codec() { } | |||||
typedef struct { | |||||
short l; | |||||
short r; | |||||
} Frame; | |||||
typedef void (*FillBufferCallback)(Frame* rx, Frame* tx, size_t size); | |||||
bool Init( | |||||
bool mcu_is_master, | |||||
int32_t sample_rate); | |||||
bool Start(size_t block_size) { | |||||
// No callback - the caller is supposed to poll with available() | |||||
return Start(block_size, NULL); | |||||
} | |||||
bool Start(size_t block_size, FillBufferCallback callback); | |||||
void Stop(); | |||||
void Fill(size_t offset); | |||||
bool set_line_input_gain(int32_t channel, int32_t gain); | |||||
bool set_line_input_gain(int32_t gain); | |||||
static Codec* GetInstance() { return instance_; } | |||||
private: | |||||
bool InitializeGPIO(); | |||||
bool InitializeControlInterface(); | |||||
bool InitializeAudioInterface(bool, int32_t); | |||||
bool InitializeCodec(bool, int32_t); | |||||
bool WriteControlRegister(uint8_t address, uint16_t data); | |||||
bool InitializeDMA(); | |||||
static Codec* instance_; | |||||
bool mcu_is_master_; | |||||
int32_t sample_rate_; | |||||
size_t block_size_; | |||||
size_t stride_; | |||||
FillBufferCallback callback_; | |||||
DMA_InitTypeDef dma_init_tx_; | |||||
DMA_InitTypeDef dma_init_rx_; | |||||
short tx_dma_buffer_[kMaxCodecBlockSize * 6 * 2]; | |||||
short rx_dma_buffer_[kMaxCodecBlockSize * 6 * 2]; | |||||
DISALLOW_COPY_AND_ASSIGN(Codec); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DRIVERS_CODEC_H_ |
@@ -0,0 +1,76 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for the debug (timing) pin. | |||||
#ifndef CLOUDS_DRIVERS_DEBUG_PIN_H_ | |||||
#define CLOUDS_DRIVERS_DEBUG_PIN_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#ifndef TEST | |||||
#include <stm32f4xx_conf.h> | |||||
#endif | |||||
namespace clouds { | |||||
class DebugPin { | |||||
public: | |||||
DebugPin() { } | |||||
~DebugPin() { } | |||||
#ifdef TEST | |||||
static void Init() { } | |||||
static void High() { } | |||||
static void Low() { } | |||||
#else | |||||
static void Init() { | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||||
GPIO_InitTypeDef gpio_init; | |||||
gpio_init.GPIO_Pin = GPIO_Pin_9; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_25MHz; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||||
GPIO_Init(GPIOA, &gpio_init); | |||||
} | |||||
static inline void High() { | |||||
GPIOA->BSRRL = GPIO_Pin_9; | |||||
} | |||||
static inline void Low() { | |||||
GPIOA->BSRRH = GPIO_Pin_9; | |||||
} | |||||
#endif | |||||
private: | |||||
DISALLOW_COPY_AND_ASSIGN(DebugPin); | |||||
}; | |||||
#define TIC DebugPin::High(); | |||||
#define TOC DebugPin::Low(); | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DRIVERS_DEBUG_PIN_H_ |
@@ -0,0 +1,64 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// UART driver for conversing with the factory testing program. | |||||
#include "clouds/drivers/debug_port.h" | |||||
#include <stm32f4xx_conf.h> | |||||
namespace clouds { | |||||
void DebugPort::Init() { | |||||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||||
// Initialize TX and RX pins. | |||||
GPIO_InitTypeDef gpio_init; | |||||
gpio_init.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_50MHz; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||||
GPIO_Init(GPIOA, &gpio_init); | |||||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); | |||||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); | |||||
// Initialize USART. | |||||
USART_InitTypeDef usart_init; | |||||
usart_init.USART_BaudRate = 9600; | |||||
usart_init.USART_WordLength = USART_WordLength_8b; | |||||
usart_init.USART_StopBits = USART_StopBits_1; | |||||
usart_init.USART_Parity = USART_Parity_No; | |||||
usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None; | |||||
usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; | |||||
USART_Init(USART1, &usart_init); | |||||
USART_Cmd(USART1, ENABLE); | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,68 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// UART driver for conversing with the factory testing program. | |||||
#ifndef CLOUDS_DRIVERS_DEBUG_PORT_H_ | |||||
#define CLOUDS_DRIVERS_DEBUG_PORT_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <stm32f4xx_conf.h> | |||||
namespace clouds { | |||||
class DebugPort { | |||||
public: | |||||
DebugPort() { } | |||||
~DebugPort() { } | |||||
void Init(); | |||||
bool writable() { | |||||
return USART1->SR & USART_FLAG_TXE; | |||||
} | |||||
bool readable() { | |||||
return USART1->SR & USART_FLAG_RXNE; | |||||
} | |||||
void Write(uint8_t byte) { | |||||
USART1->DR = byte; | |||||
} | |||||
uint8_t Read() { | |||||
return USART1->DR; | |||||
} | |||||
private: | |||||
DISALLOW_COPY_AND_ASSIGN(DebugPort); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DRIVERS_DEBUG_PORT_H_ |
@@ -0,0 +1,60 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for the gate inputs. | |||||
#include "clouds/drivers/gate_input.h" | |||||
#include <algorithm> | |||||
namespace clouds { | |||||
using namespace std; | |||||
void GateInput::Init() { | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||||
GPIO_InitTypeDef gpio_init; | |||||
gpio_init.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_25MHz; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_UP; | |||||
GPIO_Init(GPIOB, &gpio_init); | |||||
freeze_ = false; | |||||
trigger_ = false; | |||||
previous_freeze_ = false; | |||||
previous_trigger_ = false; | |||||
} | |||||
void GateInput::Read() { | |||||
previous_freeze_ = freeze_; | |||||
previous_trigger_ = trigger_; | |||||
freeze_ = !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7); | |||||
trigger_ = !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6); | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,72 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for the gate inputs. | |||||
#ifndef CLOUDS_DRIVERS_GATE_INPUT_H_ | |||||
#define CLOUDS_DRIVERS_GATE_INPUT_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <stm32f4xx_conf.h> | |||||
namespace clouds { | |||||
class GateInput { | |||||
public: | |||||
GateInput() { } | |||||
~GateInput() { } | |||||
void Init(); | |||||
void Read(); | |||||
inline bool freeze() const { return freeze_; } | |||||
inline bool trigger() const { return trigger_; } | |||||
inline bool freeze_rising_edge() const { | |||||
return freeze_ && !previous_freeze_; | |||||
} | |||||
inline bool freeze_falling_edge() const { | |||||
return !freeze_ && previous_freeze_; | |||||
} | |||||
inline bool trigger_rising_edge() const { | |||||
return trigger_ && !previous_trigger_; | |||||
} | |||||
inline bool gate() const { | |||||
return trigger_; | |||||
} | |||||
private: | |||||
bool previous_freeze_; | |||||
bool previous_trigger_; | |||||
bool freeze_; | |||||
bool trigger_; | |||||
DISALLOW_COPY_AND_ASSIGN(GateInput); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DRIVERS_GATE_INPUT_H_ |
@@ -0,0 +1,100 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for the 4 bicolor LEDs controlled by a 595. | |||||
#include "clouds/drivers/leds.h" | |||||
#include <algorithm> | |||||
namespace clouds { | |||||
using namespace std; | |||||
const uint16_t kPinClk = GPIO_Pin_5; | |||||
const uint16_t kPinEnable = GPIO_Pin_4; | |||||
const uint16_t kPinData = GPIO_Pin_5; | |||||
const uint16_t kPinFreezeLed = GPIO_Pin_9; | |||||
void Leds::Init() { | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||||
GPIO_InitTypeDef gpio_init; | |||||
gpio_init.GPIO_Pin = kPinData | kPinFreezeLed; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_25MHz; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||||
GPIO_Init(GPIOB, &gpio_init); | |||||
gpio_init.GPIO_Pin = kPinClk | kPinEnable; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_25MHz; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||||
GPIO_Init(GPIOC, &gpio_init); | |||||
Clear(); | |||||
pwm_counter_ = 0; | |||||
} | |||||
void Leds::Write() { | |||||
// Pack data. | |||||
pwm_counter_ += 32; | |||||
uint8_t data = 0; | |||||
for (uint16_t i = 0; i < 4; ++i) { | |||||
data <<= 2; | |||||
if (red_[i] && red_[i] >= pwm_counter_) { | |||||
data |= 0x1; | |||||
} | |||||
if (green_[i] && green_[i] >= pwm_counter_) { | |||||
data |= 0x2; | |||||
} | |||||
} | |||||
// Shift out. | |||||
GPIO_WriteBit(GPIOC, kPinEnable, Bit_RESET); | |||||
for (uint16_t i = 0; i < 8; ++i) { | |||||
GPIO_WriteBit(GPIOC, kPinClk, Bit_RESET); | |||||
if (data & 0x80) { | |||||
GPIO_WriteBit(GPIOB, kPinData, Bit_SET); | |||||
} else { | |||||
GPIO_WriteBit(GPIOB, kPinData, Bit_RESET); | |||||
} | |||||
data <<= 1; | |||||
GPIO_WriteBit(GPIOC, kPinClk, Bit_SET); | |||||
} | |||||
GPIO_WriteBit(GPIOC, kPinEnable, Bit_SET); | |||||
GPIO_WriteBit(GPIOB, kPinFreezeLed, static_cast<BitAction>(freeze_led_)); | |||||
} | |||||
void Leds::Clear() { | |||||
fill(&red_[0], &red_[4], 0); | |||||
fill(&green_[0], &green_[4], 0); | |||||
freeze_led_ = false; | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,109 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for the 4 bicolor LEDs controlled by a 595. | |||||
#ifndef CLOUDS_DRIVERS_LEDS_H_ | |||||
#define CLOUDS_DRIVERS_LEDS_H_ | |||||
#include <stm32f4xx_conf.h> | |||||
#include "stmlib/stmlib.h" | |||||
namespace clouds { | |||||
class Leds { | |||||
public: | |||||
Leds() { } | |||||
~Leds() { } | |||||
void Init(); | |||||
void set_status(uint8_t channel, uint8_t red, uint8_t green) { | |||||
red_[channel] = red; | |||||
green_[channel] = green; | |||||
} | |||||
void set_intensity(uint8_t channel, uint8_t value) { | |||||
uint8_t red = 0; | |||||
uint8_t green = 0; | |||||
if (value < 128) { | |||||
green = value << 1; | |||||
} else if (value < 192) { | |||||
green = 255; | |||||
red = (value - 128) << 2; | |||||
} else { | |||||
green = 255 - ((value - 192) << 2); | |||||
red = 255; | |||||
} | |||||
set_status(channel, red, green); | |||||
} | |||||
void PaintBar(int32_t db) { | |||||
if (db < 0) { | |||||
return; | |||||
} | |||||
if (db > 32767) { | |||||
db = 32767; | |||||
} | |||||
db <<= 1; | |||||
if (db >= 49152) { | |||||
set_status(3, (db - 49152) >> 6, 0); | |||||
set_status(2, 255, 255); | |||||
set_status(1, 0, 255); | |||||
set_status(0, 0, 255); | |||||
} else if (db >= 32768) { | |||||
set_status(2, (db - 32768) >> 6, (db - 32768) >> 6); | |||||
set_status(1, 0, 255); | |||||
set_status(0, 0, 255); | |||||
} else if (db >= 16384) { | |||||
set_status(1, 0, (db - 16384) >> 6); | |||||
set_status(0, 0, 255); | |||||
} else { | |||||
set_status(0, 0, db >> 6); | |||||
} | |||||
} | |||||
void Clear(); | |||||
void set_freeze(bool status) { | |||||
freeze_led_ = status; | |||||
} | |||||
void Write(); | |||||
private: | |||||
bool freeze_led_; | |||||
uint8_t pwm_counter_; | |||||
uint8_t red_[4]; | |||||
uint8_t green_[4]; | |||||
DISALLOW_COPY_AND_ASSIGN(Leds); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DRIVERS_LEDS_H_ |
@@ -0,0 +1,69 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for the front panel switches. | |||||
#include "clouds/drivers/switches.h" | |||||
#include <algorithm> | |||||
namespace clouds { | |||||
using namespace std; | |||||
void Switches::Init() { | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||||
GPIO_InitTypeDef gpio_init; | |||||
gpio_init.GPIO_Pin = GPIO_Pin_8; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_25MHz; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_UP; | |||||
GPIO_Init(GPIOB, &gpio_init); | |||||
gpio_init.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_25MHz; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_UP; | |||||
GPIO_Init(GPIOC, &gpio_init); | |||||
fill(&switch_state_[0], &switch_state_[kNumSwitches], 0xff); | |||||
} | |||||
void Switches::Debounce() { | |||||
const uint16_t pins[] = { GPIO_Pin_11, GPIO_Pin_10 }; | |||||
for (uint8_t i = 0; i < 2; ++i) { | |||||
switch_state_[i] = (switch_state_[i] << 1) | \ | |||||
GPIO_ReadInputDataBit(GPIOC, pins[i]); | |||||
} | |||||
switch_state_[kNumSwitches - 1] = (switch_state_[kNumSwitches - 1] << 1) | \ | |||||
GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8); | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,77 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for the front panel switches. | |||||
#ifndef CLOUDS_DRIVERS_SWITCHES_H_ | |||||
#define CLOUDS_DRIVERS_SWITCHES_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <stm32f4xx_conf.h> | |||||
namespace clouds { | |||||
const uint8_t kNumSwitches = 3; | |||||
class Switches { | |||||
public: | |||||
Switches() { } | |||||
~Switches() { } | |||||
void Init(); | |||||
void Debounce(); | |||||
inline bool released(uint8_t index) const { | |||||
return switch_state_[index] == 0x7f; | |||||
} | |||||
inline bool just_pressed(uint8_t index) const { | |||||
return switch_state_[index] == 0x80; | |||||
} | |||||
inline bool pressed(uint8_t index) const { | |||||
return switch_state_[index] == 0x00; | |||||
} | |||||
inline bool pressed_immediate(uint8_t index) const { | |||||
if (index == kNumSwitches - 1) { | |||||
return !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8); | |||||
} else { | |||||
const uint16_t pins[] = { GPIO_Pin_11, GPIO_Pin_10 }; | |||||
return !GPIO_ReadInputDataBit(GPIOC, pins[index]); | |||||
} | |||||
} | |||||
private: | |||||
uint8_t switch_state_[kNumSwitches]; | |||||
DISALLOW_COPY_AND_ASSIGN(Switches); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DRIVERS_SWITCHES_H_ |
@@ -0,0 +1,43 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// System level initialization. | |||||
#include "clouds/drivers/system.h" | |||||
namespace clouds { | |||||
void System::Init(bool application) { | |||||
if (application) { | |||||
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x8000); | |||||
} | |||||
} | |||||
void System::StartTimers() { | |||||
SysTick_Config(F_CPU / 1000); | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,52 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// System-level initialization. | |||||
#ifndef CLOUDS_DRIVERS_SYSTEM_H_ | |||||
#define CLOUDS_DRIVERS_SYSTEM_H_ | |||||
#include <stm32f4xx_conf.h> | |||||
#include "stmlib/stmlib.h" | |||||
namespace clouds { | |||||
class System { | |||||
public: | |||||
System() { } | |||||
~System() { } | |||||
void Init(bool application); | |||||
void StartTimers(); | |||||
private: | |||||
DISALLOW_COPY_AND_ASSIGN(System); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DRIVERS_SYSTEM_H_ |
@@ -0,0 +1,62 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Driver for reading the hardware revision pin. | |||||
#ifndef CLOUDS_DRIVERS_VERSION_H_ | |||||
#define CLOUDS_DRIVERS_VERSION_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <stm32f4xx_conf.h> | |||||
namespace clouds { | |||||
class Version { | |||||
public: | |||||
Version() { } | |||||
~Version() { } | |||||
static void Init() { | |||||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||||
GPIO_InitTypeDef gpio_init; | |||||
gpio_init.GPIO_Pin = GPIO_Pin_0; | |||||
gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||||
gpio_init.GPIO_Speed = GPIO_Speed_25MHz; | |||||
gpio_init.GPIO_PuPd = GPIO_PuPd_UP; | |||||
GPIO_Init(GPIOB, &gpio_init); | |||||
} | |||||
static inline bool revised() { | |||||
return !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0); | |||||
} | |||||
private: | |||||
DISALLOW_COPY_AND_ASSIGN(Version); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DRIVERS_VERSION_H_ |
@@ -0,0 +1,302 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Circular buffer storing audio samples. | |||||
#ifndef CLOUDS_DSP_AUDIO_BUFFER_H_ | |||||
#define CLOUDS_DSP_AUDIO_BUFFER_H_ | |||||
#include <algorithm> | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/dsp/dsp.h" | |||||
#include "stmlib/utils/dsp.h" | |||||
#include "clouds/dsp/mu_law.h" | |||||
const int32_t kCrossFadeSize = 256; | |||||
const int32_t kInterpolationTail = 8; | |||||
namespace clouds { | |||||
enum Resolution { | |||||
RESOLUTION_16_BIT, | |||||
RESOLUTION_8_BIT, | |||||
RESOLUTION_8_BIT_DITHERED, | |||||
RESOLUTION_8_BIT_MU_LAW, | |||||
}; | |||||
enum InterpolationMethod { | |||||
INTERPOLATION_ZOH, | |||||
INTERPOLATION_LINEAR, | |||||
INTERPOLATION_HERMITE | |||||
}; | |||||
template<Resolution resolution> | |||||
class AudioBuffer { | |||||
public: | |||||
AudioBuffer() { } | |||||
~AudioBuffer() { } | |||||
void Init( | |||||
void* buffer, | |||||
int32_t size, | |||||
int16_t* tail_buffer) { | |||||
s16_ = static_cast<int16_t*>(buffer); | |||||
s8_ = static_cast<int8_t*>(buffer); | |||||
size_ = size - kInterpolationTail; | |||||
write_head_ = 0; | |||||
quantization_error_ = 0.0f; | |||||
crossfade_counter_ = 0; | |||||
if (resolution == RESOLUTION_16_BIT) { | |||||
std::fill(&s16_[0], &s16_[size], 0); | |||||
} else { | |||||
std::fill( | |||||
&s8_[0], | |||||
&s8_[size], | |||||
resolution == RESOLUTION_8_BIT_MU_LAW ? 127 : 0); | |||||
} | |||||
tail_ = tail_buffer; | |||||
} | |||||
inline void Resync(int32_t head) { | |||||
write_head_ = head; | |||||
crossfade_counter_ = 0; | |||||
} | |||||
inline void Write(float in) { | |||||
if (resolution == RESOLUTION_16_BIT) { | |||||
s16_[write_head_] = stmlib::Clip16( | |||||
static_cast<int32_t>(in * 32768.0f)); | |||||
} else if (resolution == RESOLUTION_8_BIT_DITHERED) { | |||||
float sample = in * 127.0f; | |||||
sample += quantization_error_; | |||||
int32_t quantized = static_cast<int32_t>(sample); | |||||
if (quantized < -127) quantized = -127; | |||||
else if (quantized > 127) quantized = 127; | |||||
quantization_error_ = sample - static_cast<float>(in); | |||||
s8_[write_head_] = quantized; | |||||
} else if (resolution == RESOLUTION_8_BIT_MU_LAW) { | |||||
int16_t sample = stmlib::Clip16(static_cast<int32_t>(in * 32768.0f)); | |||||
s8_[write_head_] = Lin2MuLaw(sample); | |||||
} else { | |||||
s8_[write_head_] = static_cast<int8_t>( | |||||
stmlib::Clip16(in * 32768.0f) >> 8); | |||||
} | |||||
if (resolution == RESOLUTION_16_BIT) { | |||||
if (write_head_ < kInterpolationTail) { | |||||
s16_[write_head_ + size_] = s16_[write_head_]; | |||||
} | |||||
} else { | |||||
if (write_head_ < kInterpolationTail) { | |||||
s8_[write_head_ + size_] = s8_[write_head_]; | |||||
} | |||||
} | |||||
++write_head_; | |||||
if (write_head_ >= size_) { | |||||
write_head_ = 0; | |||||
} | |||||
} | |||||
inline void WriteFade( | |||||
const float* in, | |||||
int32_t size, | |||||
int32_t stride, | |||||
bool write) { | |||||
if (!write) { | |||||
// Continue recording samples to have something to crossfade with | |||||
// when recording resumes. | |||||
if (crossfade_counter_ < kCrossFadeSize) { | |||||
while (size--) { | |||||
if (crossfade_counter_ < kCrossFadeSize) { | |||||
tail_[crossfade_counter_++] = stmlib::Clip16( | |||||
static_cast<int32_t>(*in * 32767.0f)); | |||||
in += stride; | |||||
} | |||||
} | |||||
} | |||||
} else if (write && !crossfade_counter_ && | |||||
resolution == RESOLUTION_16_BIT && | |||||
write_head_ >= kInterpolationTail && write_head_ < (size_ - size)) { | |||||
// Fast write routine for the most common case. | |||||
while (size--) { | |||||
s16_[write_head_] = stmlib::Clip16( | |||||
static_cast<int32_t>(*in * 32767.0f)); | |||||
++write_head_; | |||||
in += stride; | |||||
} | |||||
} else { | |||||
while (size--) { | |||||
float sample = *in; | |||||
if (crossfade_counter_) { | |||||
--crossfade_counter_; | |||||
float tail_sample = tail_[kCrossFadeSize - crossfade_counter_]; | |||||
float gain = crossfade_counter_ * (1.0f / float(kCrossFadeSize)); | |||||
sample += (tail_sample / 32768.0f - sample) * gain; | |||||
} | |||||
Write(sample); | |||||
in += stride; | |||||
} | |||||
} | |||||
} | |||||
inline void Write(const float* in, int32_t size, int32_t stride) { | |||||
if (resolution == RESOLUTION_16_BIT | |||||
&& write_head_ >= kInterpolationTail && write_head_ < (size_ - size)) { | |||||
// Fast write routine for the most common case. | |||||
while (size--) { | |||||
s16_[write_head_] = stmlib::Clip16( | |||||
static_cast<int32_t>(*in * 32768.0f)); | |||||
++write_head_; | |||||
in += stride; | |||||
} | |||||
} else { | |||||
while (size--) { | |||||
Write(*in); | |||||
in += stride; | |||||
} | |||||
} | |||||
} | |||||
template<InterpolationMethod method> | |||||
inline float Read(int32_t integral, uint16_t fractional) const { | |||||
if (method == INTERPOLATION_ZOH) { | |||||
return ReadZOH(integral, fractional); | |||||
} else if (method == INTERPOLATION_LINEAR) { | |||||
return ReadLinear(integral, fractional); | |||||
} else if (method == INTERPOLATION_HERMITE) { | |||||
return ReadHermite(integral, fractional); | |||||
} | |||||
} | |||||
inline float ReadZOH(int32_t integral, uint16_t fractional) const { | |||||
if (integral >= size_) { | |||||
integral -= size_; | |||||
} | |||||
float x0, scale; | |||||
if (resolution == RESOLUTION_16_BIT) { | |||||
x0 = s16_[integral]; | |||||
scale = 1.0f / 32768.0f; | |||||
} else if (resolution == RESOLUTION_8_BIT_MU_LAW) { | |||||
x0 = MuLaw2Lin(s8_[integral]); | |||||
scale = 1.0f / 32768.0f; | |||||
} else { | |||||
x0 = s8_[integral]; | |||||
scale = 1.0f / 128.0f; | |||||
} | |||||
return x0 * scale; | |||||
} | |||||
inline float ReadLinear(int32_t integral, uint16_t fractional) const { | |||||
if (integral >= size_) { | |||||
integral -= size_; | |||||
} | |||||
// assert(integral >= 0 && integral < size_); | |||||
float x0, x1, scale; | |||||
float t = static_cast<float>(fractional) / 65536.0f; | |||||
if (resolution == RESOLUTION_16_BIT) { | |||||
x0 = s16_[integral]; | |||||
x1 = s16_[integral + 1]; | |||||
scale = 1.0f / 32768.0f; | |||||
} else if (resolution == RESOLUTION_8_BIT_MU_LAW) { | |||||
x0 = MuLaw2Lin(s8_[integral]); | |||||
x1 = MuLaw2Lin(s8_[integral + 1]); | |||||
scale = 1.0f / 32768.0f; | |||||
} else { | |||||
x0 = s8_[integral]; | |||||
x1 = s8_[integral + 1]; | |||||
scale = 1.0f / 128.0f; | |||||
} | |||||
return (x0 + (x1 - x0) * t) * scale; | |||||
} | |||||
inline float ReadHermite(int32_t integral, uint16_t fractional) const { | |||||
if (integral >= size_) { | |||||
integral -= size_; | |||||
} | |||||
// assert(integral >= 0 && integral < size_); | |||||
float xm1, x0, x1, x2, scale; | |||||
float t = static_cast<float>(fractional) / 65536.0f; | |||||
if (resolution == RESOLUTION_16_BIT) { | |||||
xm1 = s16_[integral]; | |||||
x0 = s16_[integral + 1]; | |||||
x1 = s16_[integral + 2]; | |||||
x2 = s16_[integral + 3]; | |||||
scale = 1.0f / 32768.0f; | |||||
} else if (resolution == RESOLUTION_8_BIT_MU_LAW) { | |||||
xm1 = MuLaw2Lin(s8_[integral]); | |||||
x0 = MuLaw2Lin(s8_[integral + 1]); | |||||
x1 = MuLaw2Lin(s8_[integral + 2]); | |||||
x2 = MuLaw2Lin(s8_[integral + 3]); | |||||
scale = 1.0f / 32768.0f; | |||||
} else { | |||||
xm1 = s8_[integral]; | |||||
x0 = s8_[integral + 1]; | |||||
x1 = s8_[integral + 2]; | |||||
x2 = s8_[integral + 3]; | |||||
scale = 1.0f / 128.0f; | |||||
} | |||||
// Laurent de Soras's Hermite interpolator. | |||||
const float c = (x1 - xm1) * 0.5f; | |||||
const float v = x0 - x1; | |||||
const float w = c + v; | |||||
const float a = w + v + (x2 - x0) * 0.5f; | |||||
const float b_neg = w + a; | |||||
return ((((a * t) - b_neg) * t + c) * t + x0) * scale; | |||||
} | |||||
inline int32_t size() const { return size_; } | |||||
inline int32_t head() const { return write_head_; } | |||||
private: | |||||
int16_t* s16_; | |||||
int8_t* s8_; | |||||
float quantization_error_; | |||||
int16_t tail_ptr_; | |||||
int32_t size_; | |||||
int32_t write_head_; | |||||
int16_t* tail_; | |||||
int32_t crossfade_counter_; | |||||
DISALLOW_COPY_AND_ASSIGN(AudioBuffer); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_AUDIO_BUFFER_H_ |
@@ -0,0 +1,88 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Search for stretch/shift splicing points by maximizing correlation. | |||||
#include "clouds/dsp/correlator.h" | |||||
#include <algorithm> | |||||
namespace clouds { | |||||
using namespace std; | |||||
void Correlator::Init(uint32_t* source, uint32_t* destination) { | |||||
source_ = source; | |||||
destination_ = destination; | |||||
offset_ = 0; | |||||
best_match_ = 0; | |||||
done_ = true; | |||||
} | |||||
void Correlator::EvaluateNextCandidate() { | |||||
if (done_) { | |||||
return; | |||||
} | |||||
uint32_t num_words = size_ >> 5; | |||||
uint32_t offset_words = candidate_ >> 5; | |||||
uint32_t offset_bits = candidate_ & 0x1f; | |||||
uint32_t* source = &source_[0]; | |||||
uint32_t* destination = &destination_[offset_words]; | |||||
uint32_t xcorr = 0; | |||||
for (uint32_t i = 0; i < num_words; ++i) { | |||||
uint32_t source_bits = source[i]; | |||||
uint32_t destination_bits = 0; | |||||
destination_bits |= destination[i] << offset_bits; | |||||
destination_bits |= destination[i + 1] >> (32 - offset_bits); | |||||
uint32_t count = ~(source_bits ^ destination_bits); | |||||
count = count - ((count >> 1) & 0x55555555); | |||||
count = (count & 0x33333333) + ((count >> 2) & 0x33333333); | |||||
count = (((count + (count >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; | |||||
xcorr += count; | |||||
} | |||||
if (xcorr > best_score_) { | |||||
best_match_ = candidate_; | |||||
best_score_ = xcorr; | |||||
} | |||||
++candidate_; | |||||
done_ = candidate_ >= size_; | |||||
} | |||||
void Correlator::StartSearch( | |||||
int32_t size, | |||||
int32_t offset, | |||||
int32_t increment) { | |||||
offset_ = offset; | |||||
increment_ = increment; | |||||
best_score_ = 0; | |||||
best_match_ = 0; | |||||
candidate_ = 0; | |||||
size_ = size; | |||||
done_ = false; | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,88 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Search for stretch/shift splicing points by maximizing correlation. | |||||
// Correlation is computed by XOR-ing the bit sign of samples - this allows | |||||
// 32 samples to be matched in one single XOR operation. | |||||
#ifndef CLOUDS_DSP_CORRELATOR_H_ | |||||
#define CLOUDS_DSP_CORRELATOR_H_ | |||||
#include "stmlib/stmlib.h" | |||||
namespace clouds { | |||||
class Correlator { | |||||
public: | |||||
Correlator() { } | |||||
~Correlator() { } | |||||
void Init(uint32_t* source, uint32_t* destination); | |||||
void StartSearch(int32_t size, int32_t offset, int32_t increment); | |||||
inline int32_t best_match() const { | |||||
return offset_ + (best_match_ * (increment_ >> 4) >> 12); | |||||
} | |||||
inline void EvaluateSomeCandidates() { | |||||
size_t num_candidates = (size_ >> 2) + 16; | |||||
while (num_candidates) { | |||||
EvaluateNextCandidate(); | |||||
--num_candidates; | |||||
} | |||||
} | |||||
void EvaluateNextCandidate(); | |||||
inline uint32_t* source() { return source_; } | |||||
inline uint32_t* destination() { return destination_; } | |||||
inline int32_t candidate() { return candidate_; } | |||||
inline bool done() { return done_; } | |||||
private: | |||||
uint32_t* source_; | |||||
uint32_t* destination_; | |||||
int32_t offset_; | |||||
int32_t increment_; | |||||
int32_t size_; | |||||
int32_t candidate_; | |||||
uint32_t best_score_; | |||||
int32_t best_match_; | |||||
int32_t trace_; | |||||
bool done_; | |||||
DISALLOW_COPY_AND_ASSIGN(Correlator); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_CORRELATOR_H_ |
@@ -0,0 +1,44 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Audio processing frames. | |||||
#ifndef CLOUDS_DSP_FRAME_H_ | |||||
#define CLOUDS_DSP_FRAME_H_ | |||||
#include "stmlib/stmlib.h" | |||||
namespace clouds { | |||||
const int32_t kMaxNumChannels = 2; | |||||
const size_t kMaxBlockSize = 32; | |||||
typedef struct { short l; short r; } ShortFrame; | |||||
typedef struct { float l; float r; } FloatFrame; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_FRAME_H_ |
@@ -0,0 +1,112 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// AP diffusion network. | |||||
#ifndef CLOUDS_DSP_FX_DIFFUSER_H_ | |||||
#define CLOUDS_DSP_FX_DIFFUSER_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "clouds/dsp/fx/fx_engine.h" | |||||
namespace clouds { | |||||
class Diffuser { | |||||
public: | |||||
Diffuser() { } | |||||
~Diffuser() { } | |||||
void Init(float* buffer) { | |||||
engine_.Init(buffer); | |||||
} | |||||
void Process(FloatFrame* in_out, size_t size) { | |||||
typedef E::Reserve<126, | |||||
E::Reserve<180, | |||||
E::Reserve<269, | |||||
E::Reserve<444, | |||||
E::Reserve<151, | |||||
E::Reserve<205, | |||||
E::Reserve<245, | |||||
E::Reserve<405> > > > > > > > Memory; | |||||
E::DelayLine<Memory, 0> apl1; | |||||
E::DelayLine<Memory, 1> apl2; | |||||
E::DelayLine<Memory, 2> apl3; | |||||
E::DelayLine<Memory, 3> apl4; | |||||
E::DelayLine<Memory, 4> apr1; | |||||
E::DelayLine<Memory, 5> apr2; | |||||
E::DelayLine<Memory, 6> apr3; | |||||
E::DelayLine<Memory, 7> apr4; | |||||
E::Context c; | |||||
const float kap = 0.625f; | |||||
while (size--) { | |||||
engine_.Start(&c); | |||||
float wet = 0.0f; | |||||
c.Read(in_out->l); | |||||
c.Read(apl1 TAIL, kap); | |||||
c.WriteAllPass(apl1, -kap); | |||||
c.Read(apl2 TAIL, kap); | |||||
c.WriteAllPass(apl2, -kap); | |||||
c.Read(apl3 TAIL, kap); | |||||
c.WriteAllPass(apl3, -kap); | |||||
c.Read(apl4 TAIL, kap); | |||||
c.WriteAllPass(apl4, -kap); | |||||
c.Write(wet, 0.0f); | |||||
in_out->l += amount_ * (wet - in_out->l); | |||||
c.Read(in_out->r); | |||||
c.Read(apr1 TAIL, kap); | |||||
c.WriteAllPass(apr1, -kap); | |||||
c.Read(apr2 TAIL, kap); | |||||
c.WriteAllPass(apr2, -kap); | |||||
c.Read(apr3 TAIL, kap); | |||||
c.WriteAllPass(apr3, -kap); | |||||
c.Read(apr4 TAIL, kap); | |||||
c.WriteAllPass(apr4, -kap); | |||||
c.Write(wet, 0.0f); | |||||
in_out->r += amount_ * (wet - in_out->r); | |||||
++in_out; | |||||
} | |||||
} | |||||
void set_amount(float amount) { | |||||
amount_ = amount; | |||||
} | |||||
private: | |||||
typedef FxEngine<2048, FORMAT_32_BIT> E; | |||||
E engine_; | |||||
float amount_; | |||||
DISALLOW_COPY_AND_ASSIGN(Diffuser); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_FX_DIFFUSER_H_ |
@@ -0,0 +1,302 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Base class for building reverbs. | |||||
#ifndef CLOUDS_DSP_FX_FX_ENGINE_H_ | |||||
#define CLOUDS_DSP_FX_FX_ENGINE_H_ | |||||
#include <algorithm> | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/dsp/dsp.h" | |||||
#include "stmlib/dsp/cosine_oscillator.h" | |||||
namespace clouds { | |||||
#define TAIL , -1 | |||||
enum Format { | |||||
FORMAT_12_BIT, | |||||
FORMAT_16_BIT, | |||||
FORMAT_32_BIT | |||||
}; | |||||
enum LFOIndex { | |||||
LFO_1, | |||||
LFO_2 | |||||
}; | |||||
template<Format format> | |||||
struct DataType { }; | |||||
template<> | |||||
struct DataType<FORMAT_12_BIT> { | |||||
typedef uint16_t T; | |||||
static inline float Decompress(T value) { | |||||
return static_cast<float>(static_cast<int16_t>(value)) / 4096.0f; | |||||
} | |||||
static inline T Compress(float value) { | |||||
return static_cast<uint16_t>( | |||||
stmlib::Clip16(static_cast<int32_t>(value * 4096.0f))); | |||||
} | |||||
}; | |||||
template<> | |||||
struct DataType<FORMAT_16_BIT> { | |||||
typedef uint16_t T; | |||||
static inline float Decompress(T value) { | |||||
return static_cast<float>(static_cast<int16_t>(value)) / 32768.0f; | |||||
} | |||||
static inline T Compress(float value) { | |||||
return static_cast<uint16_t>( | |||||
stmlib::Clip16(static_cast<int32_t>(value * 32768.0f))); | |||||
} | |||||
}; | |||||
template<> | |||||
struct DataType<FORMAT_32_BIT> { | |||||
typedef float T; | |||||
static inline float Decompress(T value) { | |||||
return value;; | |||||
} | |||||
static inline T Compress(float value) { | |||||
return value; | |||||
} | |||||
}; | |||||
template< | |||||
size_t size, | |||||
Format format = FORMAT_12_BIT> | |||||
class FxEngine { | |||||
public: | |||||
typedef typename DataType<format>::T T; | |||||
FxEngine() { } | |||||
~FxEngine() { } | |||||
void Init(T* buffer) { | |||||
buffer_ = buffer; | |||||
Clear(); | |||||
} | |||||
void Clear() { | |||||
std::fill(&buffer_[0], &buffer_[size], 0); | |||||
write_ptr_ = 0; | |||||
} | |||||
struct Empty { }; | |||||
template<int32_t l, typename T = Empty> | |||||
struct Reserve { | |||||
typedef T Tail; | |||||
enum { | |||||
length = l | |||||
}; | |||||
}; | |||||
template<typename Memory, int32_t index> | |||||
struct DelayLine { | |||||
enum { | |||||
length = DelayLine<typename Memory::Tail, index - 1>::length, | |||||
base = DelayLine<Memory, index - 1>::base + DelayLine<Memory, index - 1>::length + 1 | |||||
}; | |||||
}; | |||||
template<typename Memory> | |||||
struct DelayLine<Memory, 0> { | |||||
enum { | |||||
length = Memory::length, | |||||
base = 0 | |||||
}; | |||||
}; | |||||
class Context { | |||||
friend class FxEngine; | |||||
public: | |||||
Context() { } | |||||
~Context() { } | |||||
inline void Load(float value) { | |||||
accumulator_ = value; | |||||
} | |||||
inline void Read(float value, float scale) { | |||||
accumulator_ += value * scale; | |||||
} | |||||
inline void Read(float value) { | |||||
accumulator_ += value; | |||||
} | |||||
inline void Write(float& value) { | |||||
value = accumulator_; | |||||
} | |||||
inline void Write(float& value, float scale) { | |||||
value = accumulator_; | |||||
accumulator_ *= scale; | |||||
} | |||||
template<typename D> | |||||
inline void Write(D& d, int32_t offset, float scale) { | |||||
STATIC_ASSERT(D::base + D::length <= size, delay_memory_full); | |||||
T w = DataType<format>::Compress(accumulator_); | |||||
if (offset == -1) { | |||||
buffer_[(write_ptr_ + D::base + D::length - 1) & MASK] = w; | |||||
} else { | |||||
buffer_[(write_ptr_ + D::base + offset) & MASK] = w; | |||||
} | |||||
accumulator_ *= scale; | |||||
} | |||||
template<typename D> | |||||
inline void Write(D& d, float scale) { | |||||
Write(d, 0, scale); | |||||
} | |||||
template<typename D> | |||||
inline void WriteAllPass(D& d, int32_t offset, float scale) { | |||||
Write(d, offset, scale); | |||||
accumulator_ += previous_read_; | |||||
} | |||||
template<typename D> | |||||
inline void WriteAllPass(D& d, float scale) { | |||||
WriteAllPass(d, 0, scale); | |||||
} | |||||
template<typename D> | |||||
inline void Read(D& d, int32_t offset, float scale) { | |||||
STATIC_ASSERT(D::base + D::length <= size, delay_memory_full); | |||||
T r; | |||||
if (offset == -1) { | |||||
r = buffer_[(write_ptr_ + D::base + D::length - 1) & MASK]; | |||||
} else { | |||||
r = buffer_[(write_ptr_ + D::base + offset) & MASK]; | |||||
} | |||||
float r_f = DataType<format>::Decompress(r); | |||||
previous_read_ = r_f; | |||||
accumulator_ += r_f * scale; | |||||
} | |||||
template<typename D> | |||||
inline void Read(D& d, float scale) { | |||||
Read(d, 0, scale); | |||||
} | |||||
inline void Lp(float& state, float coefficient) { | |||||
state += coefficient * (accumulator_ - state); | |||||
accumulator_ = state; | |||||
} | |||||
inline void Hp(float& state, float coefficient) { | |||||
state += coefficient * (accumulator_ - state); | |||||
accumulator_ -= state; | |||||
} | |||||
template<typename D> | |||||
inline void Interpolate(D& d, float offset, float scale) { | |||||
STATIC_ASSERT(D::base + D::length <= size, delay_memory_full); | |||||
MAKE_INTEGRAL_FRACTIONAL(offset); | |||||
float a = DataType<format>::Decompress( | |||||
buffer_[(write_ptr_ + offset_integral + D::base) & MASK]); | |||||
float b = DataType<format>::Decompress( | |||||
buffer_[(write_ptr_ + offset_integral + D::base + 1) & MASK]); | |||||
float x = a + (b - a) * offset_fractional; | |||||
previous_read_ = x; | |||||
accumulator_ += x * scale; | |||||
} | |||||
template<typename D> | |||||
inline void Interpolate( | |||||
D& d, float offset, LFOIndex index, float amplitude, float scale) { | |||||
STATIC_ASSERT(D::base + D::length <= size, delay_memory_full); | |||||
offset += amplitude * lfo_value_[index]; | |||||
MAKE_INTEGRAL_FRACTIONAL(offset); | |||||
float a = DataType<format>::Decompress( | |||||
buffer_[(write_ptr_ + offset_integral + D::base) & MASK]); | |||||
float b = DataType<format>::Decompress( | |||||
buffer_[(write_ptr_ + offset_integral + D::base + 1) & MASK]); | |||||
float x = a + (b - a) * offset_fractional; | |||||
previous_read_ = x; | |||||
accumulator_ += x * scale; | |||||
} | |||||
private: | |||||
float accumulator_; | |||||
float previous_read_; | |||||
float lfo_value_[2]; | |||||
T* buffer_; | |||||
int32_t write_ptr_; | |||||
DISALLOW_COPY_AND_ASSIGN(Context); | |||||
}; | |||||
inline void SetLFOFrequency(LFOIndex index, float frequency) { | |||||
lfo_[index].template Init<stmlib::COSINE_OSCILLATOR_APPROXIMATE>( | |||||
frequency * 32.0f); | |||||
} | |||||
inline void Start(Context* c) { | |||||
--write_ptr_; | |||||
if (write_ptr_ < 0) { | |||||
write_ptr_ += size; | |||||
} | |||||
c->accumulator_ = 0.0f; | |||||
c->previous_read_ = 0.0f; | |||||
c->buffer_ = buffer_; | |||||
c->write_ptr_ = write_ptr_; | |||||
if ((write_ptr_ & 31) == 0) { | |||||
c->lfo_value_[0] = lfo_[0].Next(); | |||||
c->lfo_value_[1] = lfo_[1].Next(); | |||||
} else { | |||||
c->lfo_value_[0] = lfo_[0].value(); | |||||
c->lfo_value_[1] = lfo_[1].value(); | |||||
} | |||||
} | |||||
private: | |||||
enum { | |||||
MASK = size - 1 | |||||
}; | |||||
int32_t write_ptr_; | |||||
T* buffer_; | |||||
stmlib::CosineOscillator lfo_[2]; | |||||
DISALLOW_COPY_AND_ASSIGN(FxEngine); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_FX_FX_ENGINE_H_ |
@@ -0,0 +1,112 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Pitch shifter. | |||||
#ifndef CLOUDS_DSP_FX_PITCH_SHIFTER_H_ | |||||
#define CLOUDS_DSP_FX_PITCH_SHIFTER_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "clouds/dsp/frame.h" | |||||
#include "clouds/dsp/fx/fx_engine.h" | |||||
namespace clouds { | |||||
class PitchShifter { | |||||
public: | |||||
PitchShifter() { } | |||||
~PitchShifter() { } | |||||
void Init(float* buffer,float initialPhase) { | |||||
engine_.Init(buffer); | |||||
phase_ = initialPhase; | |||||
size_ = 2047.0f; | |||||
} | |||||
void Clear() { | |||||
engine_.Clear(); | |||||
} | |||||
//inline void Process(FloatFrame* input_output, size_t size) { | |||||
// while (size--) { | |||||
// Process(input_output); | |||||
// ++input_output; | |||||
// } | |||||
//} | |||||
void Process(FloatFrame* input_output,bool useTriangleWindow) { | |||||
typedef E::Reserve<2047, E::Reserve<2047> > Memory; | |||||
E::DelayLine<Memory, 0> grain; | |||||
E::Context c; | |||||
engine_.Start(&c); | |||||
phase_ += (1.0f - ratio_) / size_; | |||||
if (phase_ >= 1.0f) { | |||||
phase_ -= 1.0f; | |||||
} | |||||
if (phase_ <= 0.0f) { | |||||
phase_ += 1.0f; | |||||
} | |||||
float tri = 1.0f; | |||||
if(useTriangleWindow) { | |||||
tri = 2.0f * (phase_ >= 0.5f ? 1.0f - phase_ : phase_); | |||||
} | |||||
float phase = phase_ * size_; | |||||
float half = phase + size_ * 0.5f; | |||||
if (half >= size_) { | |||||
half -= size_; | |||||
} | |||||
c.Read(input_output->l, 1.0f); | |||||
c.Write(grain, 0.0f); | |||||
c.Interpolate(grain, phase, tri); | |||||
c.Interpolate(grain, half, 1.0f - tri); | |||||
c.Write(input_output->l, 0.0f); | |||||
} | |||||
inline void set_ratio(float ratio) { | |||||
ratio_ = ratio; | |||||
} | |||||
inline void set_size(float size) { | |||||
float target_size = 128.0f + (2047.0f - 128.0f) * size * size * size; | |||||
ONE_POLE(size_, target_size, 0.05f) | |||||
} | |||||
private: | |||||
typedef FxEngine<4096, FORMAT_32_BIT> E; | |||||
E engine_; | |||||
float phase_; | |||||
float ratio_; | |||||
float size_; | |||||
DISALLOW_COPY_AND_ASSIGN(PitchShifter); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_FX_MINI_CHORUS_H_ |
@@ -0,0 +1,180 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Reverb. | |||||
#ifndef CLOUDS_DSP_FX_REVERB_H_ | |||||
#define CLOUDS_DSP_FX_REVERB_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "clouds/dsp/fx/fx_engine.h" | |||||
namespace clouds { | |||||
class Reverb { | |||||
public: | |||||
Reverb() { } | |||||
~Reverb() { } | |||||
void Init(uint16_t* buffer) { | |||||
engine_.Init(buffer); | |||||
engine_.SetLFOFrequency(LFO_1, 0.5f / 32000.0f); | |||||
engine_.SetLFOFrequency(LFO_2, 0.3f / 32000.0f); | |||||
lp_ = 0.7f; | |||||
diffusion_ = 0.625f; | |||||
} | |||||
void Process(FloatFrame* in_out, size_t size) { | |||||
// This is the Griesinger topology described in the Dattorro paper | |||||
// (4 AP diffusers on the input, then a loop of 2x 2AP+1Delay). | |||||
// Modulation is applied in the loop of the first diffuser AP for additional | |||||
// smearing; and to the two long delays for a slow shimmer/chorus effect. | |||||
typedef E::Reserve<113, | |||||
E::Reserve<162, | |||||
E::Reserve<241, | |||||
E::Reserve<399, | |||||
E::Reserve<1653, | |||||
E::Reserve<2038, | |||||
E::Reserve<3411, | |||||
E::Reserve<1913, | |||||
E::Reserve<1663, | |||||
E::Reserve<4782> > > > > > > > > > Memory; | |||||
E::DelayLine<Memory, 0> ap1; | |||||
E::DelayLine<Memory, 1> ap2; | |||||
E::DelayLine<Memory, 2> ap3; | |||||
E::DelayLine<Memory, 3> ap4; | |||||
E::DelayLine<Memory, 4> dap1a; | |||||
E::DelayLine<Memory, 5> dap1b; | |||||
E::DelayLine<Memory, 6> del1; | |||||
E::DelayLine<Memory, 7> dap2a; | |||||
E::DelayLine<Memory, 8> dap2b; | |||||
E::DelayLine<Memory, 9> del2; | |||||
E::Context c; | |||||
const float kap = diffusion_; | |||||
const float klp = lp_; | |||||
const float krt = reverb_time_; | |||||
const float amount = amount_; | |||||
const float gain = input_gain_; | |||||
float lp_1 = lp_decay_1_; | |||||
float lp_2 = lp_decay_2_; | |||||
while (size--) { | |||||
float wet; | |||||
float apout = 0.0f; | |||||
engine_.Start(&c); | |||||
// Smear AP1 inside the loop. | |||||
c.Interpolate(ap1, 10.0f, LFO_1, 60.0f, 1.0f); | |||||
c.Write(ap1, 100, 0.0f); | |||||
c.Read(in_out->l + in_out->r, gain); | |||||
// Diffuse through 4 allpasses. | |||||
c.Read(ap1 TAIL, kap); | |||||
c.WriteAllPass(ap1, -kap); | |||||
c.Read(ap2 TAIL, kap); | |||||
c.WriteAllPass(ap2, -kap); | |||||
c.Read(ap3 TAIL, kap); | |||||
c.WriteAllPass(ap3, -kap); | |||||
c.Read(ap4 TAIL, kap); | |||||
c.WriteAllPass(ap4, -kap); | |||||
c.Write(apout); | |||||
// Main reverb loop. | |||||
c.Load(apout); | |||||
c.Interpolate(del2, 4680.0f, LFO_2, 100.0f, krt); | |||||
c.Lp(lp_1, klp); | |||||
c.Read(dap1a TAIL, -kap); | |||||
c.WriteAllPass(dap1a, kap); | |||||
c.Read(dap1b TAIL, kap); | |||||
c.WriteAllPass(dap1b, -kap); | |||||
c.Write(del1, 2.0f); | |||||
c.Write(wet, 0.0f); | |||||
in_out->l += (wet - in_out->l) * amount; | |||||
c.Load(apout); | |||||
// c.Interpolate(del1, 4450.0f, LFO_1, 50.0f, krt); | |||||
c.Read(del1 TAIL, krt); | |||||
c.Lp(lp_2, klp); | |||||
c.Read(dap2a TAIL, kap); | |||||
c.WriteAllPass(dap2a, -kap); | |||||
c.Read(dap2b TAIL, -kap); | |||||
c.WriteAllPass(dap2b, kap); | |||||
c.Write(del2, 2.0f); | |||||
c.Write(wet, 0.0f); | |||||
in_out->r += (wet - in_out->r) * amount; | |||||
++in_out; | |||||
} | |||||
lp_decay_1_ = lp_1; | |||||
lp_decay_2_ = lp_2; | |||||
} | |||||
inline void set_amount(float amount) { | |||||
amount_ = amount; | |||||
} | |||||
inline void set_input_gain(float input_gain) { | |||||
input_gain_ = input_gain; | |||||
} | |||||
inline void set_time(float reverb_time) { | |||||
reverb_time_ = reverb_time; | |||||
} | |||||
inline void set_diffusion(float diffusion) { | |||||
diffusion_ = diffusion; | |||||
} | |||||
inline void set_lp(float lp) { | |||||
lp_ = lp; | |||||
} | |||||
private: | |||||
typedef FxEngine<16384, FORMAT_12_BIT> E; | |||||
E engine_; | |||||
float amount_; | |||||
float input_gain_; | |||||
float reverb_time_; | |||||
float diffusion_; | |||||
float lp_; | |||||
float lp_decay_1_; | |||||
float lp_decay_2_; | |||||
DISALLOW_COPY_AND_ASSIGN(Reverb); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_FX_REVERB_H_ |
@@ -0,0 +1,205 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Single grain synthesis. | |||||
#ifndef CLOUDS_DSP_GRAIN_H_ | |||||
#define CLOUDS_DSP_GRAIN_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/dsp/dsp.h" | |||||
#include "clouds/dsp/audio_buffer.h" | |||||
#include "clouds/resources.h" | |||||
namespace clouds { | |||||
enum GrainQuality { | |||||
GRAIN_QUALITY_LOW, | |||||
GRAIN_QUALITY_MEDIUM, | |||||
GRAIN_QUALITY_HIGH | |||||
}; | |||||
class Grain { | |||||
public: | |||||
Grain() { } | |||||
~Grain() { } | |||||
void Init() { | |||||
active_ = false; | |||||
envelope_phase_ = 2.0f; | |||||
} | |||||
void Start( | |||||
int32_t pre_delay, | |||||
int32_t buffer_size, | |||||
int32_t start, | |||||
int32_t width, | |||||
int32_t phase_increment, | |||||
float window_shape, | |||||
float gain_l, | |||||
float gain_r, | |||||
GrainQuality recommended_quality) { | |||||
pre_delay_ = pre_delay; | |||||
width_ = width; | |||||
first_sample_ = (start + buffer_size) % buffer_size; | |||||
phase_increment_ = phase_increment; | |||||
phase_ = 0; | |||||
envelope_phase_ = 0.0f; | |||||
envelope_phase_increment_ = 2.0f / static_cast<float>(width); | |||||
if (window_shape >= 0.5f) { | |||||
envelope_smoothness_ = (window_shape - 0.5f) * 2.0f; | |||||
envelope_slope_ = 0.0f; | |||||
} else { | |||||
envelope_smoothness_ = 0.0f; | |||||
envelope_slope_ = 0.5f / (window_shape + 0.01f); | |||||
} | |||||
active_ = true; | |||||
gain_l_ = gain_l; | |||||
gain_r_ = gain_r; | |||||
recommended_quality_ = recommended_quality; | |||||
} | |||||
template<bool use_lut_for_envelope, GrainQuality quality> | |||||
inline void RenderEnvelope(float* destination, size_t size) { | |||||
const float increment = envelope_phase_increment_; | |||||
const float smoothness = envelope_smoothness_; | |||||
const float slope = envelope_slope_; | |||||
float phase = envelope_phase_; | |||||
while (size--) { | |||||
float gain = phase; | |||||
gain = gain >= 1.0f ? 2.0f - gain : gain; | |||||
if (use_lut_for_envelope) { | |||||
if (quality == GRAIN_QUALITY_HIGH) { | |||||
float window = 0.0f; | |||||
window = stmlib::Interpolate(lut_window, gain, 4096.0f); | |||||
gain += smoothness * (window - gain); | |||||
} | |||||
} else { | |||||
if (quality >= GRAIN_QUALITY_MEDIUM) { | |||||
gain *= slope; | |||||
if (gain >= 1.0f) gain = 1.0f; | |||||
} | |||||
} | |||||
phase += increment; | |||||
if (phase >= 2.0f) { | |||||
*destination = -1.0f; | |||||
break; | |||||
} | |||||
*destination++ = gain; | |||||
} | |||||
envelope_phase_ = phase; | |||||
} | |||||
template<int32_t num_channels, GrainQuality quality, Resolution resolution> | |||||
inline void OverlapAdd( | |||||
const AudioBuffer<resolution>* buffer, | |||||
float* destination, | |||||
float* envelope, | |||||
size_t size) { | |||||
if (!active_) { | |||||
return; | |||||
} | |||||
// Rendering is done on 32-sample long blocks. The pre-delay allows grains | |||||
// to start at arbitrary samples within a block, rather than at block | |||||
// boundaries. | |||||
while (pre_delay_ && size) { | |||||
destination += 2; | |||||
--size; | |||||
--pre_delay_; | |||||
} | |||||
// Pre-render the envelope in one pass. | |||||
if (envelope_smoothness_ == 0.0f) { | |||||
RenderEnvelope<false, quality>(envelope, size); | |||||
} else { | |||||
RenderEnvelope<true, quality>(envelope, size); | |||||
} | |||||
const int32_t phase_increment = phase_increment_; | |||||
const int32_t first_sample = first_sample_; | |||||
const float gain_l = gain_l_; | |||||
const float gain_r = gain_r_; | |||||
int32_t phase = phase_; | |||||
while (size--) { | |||||
int32_t sample_index = first_sample + (phase >> 16); | |||||
float gain = *envelope++; | |||||
if (gain == -1.0f) { | |||||
active_ = false; | |||||
break; | |||||
} | |||||
float l = buffer[0].template Read<InterpolationMethod(quality)>( | |||||
sample_index, phase & 65535) * gain; | |||||
if (num_channels == 1) { | |||||
*destination++ += l * gain_l; | |||||
*destination++ += l * gain_r; | |||||
} else if (num_channels == 2) { | |||||
float r = buffer[1].template Read<InterpolationMethod(quality)>( | |||||
sample_index, phase & 65535) * gain; | |||||
*destination++ += l * gain_l + r * (1.0f - gain_r); | |||||
*destination++ += r * gain_r + l * (1.0f - gain_l); | |||||
} | |||||
phase += phase_increment; | |||||
} | |||||
phase_ = phase; | |||||
} | |||||
inline bool active() { return active_; } | |||||
inline GrainQuality recommended_quality() const { | |||||
return recommended_quality_; | |||||
} | |||||
private: | |||||
int32_t first_sample_; | |||||
int32_t width_; | |||||
int32_t phase_; | |||||
int32_t phase_increment_; | |||||
int32_t pre_delay_; | |||||
float envelope_smoothness_; | |||||
float envelope_slope_; | |||||
float envelope_phase_; | |||||
float envelope_phase_increment_; | |||||
float gain_l_; | |||||
float gain_r_; | |||||
bool active_; | |||||
GrainQuality recommended_quality_; | |||||
DISALLOW_COPY_AND_ASSIGN(Grain); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_GRAIN_H_ |
@@ -0,0 +1,289 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Main processing class. | |||||
#include "clouds/dsp/granular_processor.h" | |||||
#include <cstring> | |||||
#include "clouds/drivers/debug_pin.h" | |||||
#include "stmlib/dsp/parameter_interpolator.h" | |||||
#include "stmlib/utils/buffer_allocator.h" | |||||
#include "clouds/resources.h" | |||||
namespace clouds { | |||||
using namespace std; | |||||
using namespace stmlib; | |||||
void GranularProcessor::Init( | |||||
void* large_buffer, size_t large_buffer_size, | |||||
void* small_buffer, size_t small_buffer_size) { | |||||
buffer_[0] = large_buffer; | |||||
buffer_[1] = small_buffer; | |||||
buffer_size_[0] = large_buffer_size; | |||||
buffer_size_[1] = small_buffer_size; | |||||
num_channels_ = 1; | |||||
low_fidelity_ = false; | |||||
bypass_ = false; | |||||
src_down_.Init(); | |||||
src_up_.Init(); | |||||
ResetFilters(); | |||||
previous_playback_mode_ = PLAYBACK_MODE_LAST; | |||||
reset_buffers_ = true; | |||||
dry_wet_ = 0.0f; | |||||
} | |||||
void GranularProcessor::ResetFilters() { | |||||
for (int32_t i = 0; i < 2; ++i) { | |||||
fb_filter_[i].Init(); | |||||
} | |||||
} | |||||
void GranularProcessor::ProcessGranular( | |||||
FloatFrame* input, | |||||
FloatFrame* output, | |||||
size_t size) { | |||||
parameters_.spectral.quantization = parameters_.texture; | |||||
parameters_.spectral.refresh_rate = 0.01f + 0.99f * parameters_.density; | |||||
float warp = parameters_.size - 0.5f; | |||||
parameters_.spectral.warp = 4.0f * warp * warp * warp + 0.5f; | |||||
float randomization = parameters_.density - 0.5f; | |||||
randomization *= randomization * 4.2f; | |||||
randomization -= 0.05f; | |||||
CONSTRAIN(randomization, 0.0f, 1.0f); | |||||
parameters_.spectral.phase_randomization = randomization; | |||||
phase_vocoder_.Process(parameters_, input, output, size); | |||||
if (num_channels_ == 1) { | |||||
for (size_t i = 0; i < size; ++i) { | |||||
output[i].r = output[i].l; | |||||
} | |||||
} | |||||
} | |||||
void GranularProcessor::Process( | |||||
ShortFrame* input, | |||||
ShortFrame* output, | |||||
size_t size) { | |||||
// TIC | |||||
if (bypass_) { | |||||
copy(&input[0], &input[size], &output[0]); | |||||
return; | |||||
} | |||||
if (silence_ || reset_buffers_ || | |||||
previous_playback_mode_ != playback_mode_) { | |||||
short* output_samples = &output[0].l; | |||||
fill(&output_samples[0], &output_samples[size << 1], 0); | |||||
return; | |||||
} | |||||
// Convert input buffers to float, and mixdown for mono processing. | |||||
for (size_t i = 0; i < size; ++i) { | |||||
in_[i].l = static_cast<float>(input[i].l) / 32768.0f; | |||||
in_[i].r = static_cast<float>(input[i].r) / 32768.0f; | |||||
} | |||||
if (num_channels_ == 1) { | |||||
for (size_t i = 0; i < size; ++i) { | |||||
in_[i].l = (in_[i].l + in_[i].r) * 0.5f; | |||||
in_[i].r = in_[i].l; | |||||
} | |||||
} | |||||
// Apply feedback, with high-pass filtering to prevent build-ups at very | |||||
// low frequencies (causing large DC swings). | |||||
ONE_POLE(freeze_lp_, parameters_.freeze ? 1.0f : 0.0f, 0.0005f) | |||||
float feedback = parameters_.feedback; | |||||
float cutoff = (20.0f + 100.0f * feedback * feedback) / sample_rate(); | |||||
fb_filter_[0].set_f_q<FREQUENCY_FAST>(cutoff, 1.0f); | |||||
fb_filter_[1].set(fb_filter_[0]); | |||||
fb_filter_[0].Process<FILTER_MODE_HIGH_PASS>(&fb_[0].l, &fb_[0].l, size, 2); | |||||
fb_filter_[1].Process<FILTER_MODE_HIGH_PASS>(&fb_[0].r, &fb_[0].r, size, 2); | |||||
float fb_gain = feedback * (1.0f - freeze_lp_); | |||||
for (size_t i = 0; i < size; ++i) { | |||||
in_[i].l += fb_gain * ( | |||||
SoftLimit(fb_gain * 1.4f * fb_[i].l + in_[i].l) - in_[i].l); | |||||
in_[i].r += fb_gain * ( | |||||
SoftLimit(fb_gain * 1.4f * fb_[i].r + in_[i].r) - in_[i].r); | |||||
} | |||||
ProcessGranular(in_, out_, size); | |||||
// This is what is fed back. Reverb is not fed back. | |||||
copy(&out_[0], &out_[size], &fb_[0]); | |||||
const float post_gain = 1.2f; | |||||
ParameterInterpolator dry_wet_mod(&dry_wet_, parameters_.dry_wet, size); | |||||
for (size_t i = 0; i < size; ++i) { | |||||
float dry_wet = dry_wet_mod.Next(); | |||||
float fade_in = Interpolate(lut_xfade_in, dry_wet, 16.0f); | |||||
float fade_out = Interpolate(lut_xfade_out, dry_wet, 16.0f); | |||||
float l = static_cast<float>(input[i].l) / 32768.0f * fade_out; | |||||
float r = static_cast<float>(input[i].r) / 32768.0f * fade_out; | |||||
l += out_[i].l * post_gain * fade_in; | |||||
r += out_[i].r * post_gain * fade_in; | |||||
output[i].l = SoftConvert(l); | |||||
output[i].r = SoftConvert(r); | |||||
} | |||||
} | |||||
void GranularProcessor::PreparePersistentData() { | |||||
persistent_state_.write_head[0] = buffer_16_[0].head(); | |||||
persistent_state_.write_head[1] = buffer_16_[1].head(); | |||||
persistent_state_.quality = quality(); | |||||
persistent_state_.spectral = playback_mode() == PLAYBACK_MODE_SPECTRAL; | |||||
} | |||||
void GranularProcessor::GetPersistentData( | |||||
PersistentBlock* block, size_t *num_blocks) { | |||||
PersistentBlock* first_block = block; | |||||
block->tag = FourCC<'s', 't', 'a', 't'>::value; | |||||
block->data = &persistent_state_; | |||||
block->size = sizeof(PersistentState); | |||||
++block; | |||||
// Create save block holding the audio buffers. | |||||
for (int32_t i = 0; i < num_channels_; ++i) { | |||||
block->tag = FourCC<'b', 'u', 'f', 'f'>::value; | |||||
block->data = buffer_[i]; | |||||
block->size = buffer_size_[num_channels_ - 1]; | |||||
++block; | |||||
} | |||||
*num_blocks = block - first_block; | |||||
} | |||||
bool GranularProcessor::LoadPersistentData(const uint32_t* data) { | |||||
// Force a silent output while the swapping of buffers takes place. | |||||
silence_ = true; | |||||
PersistentBlock block[4]; | |||||
size_t num_blocks; | |||||
GetPersistentData(block, &num_blocks); | |||||
for (size_t i = 0; i < num_blocks; ++i) { | |||||
// Check that the format is correct. | |||||
if (block[i].tag != data[0] || block[i].size != data[1]) { | |||||
silence_ = false; | |||||
return false; | |||||
} | |||||
// All good. Load the data. 2 words have already been used for the block tag | |||||
// and the block size. | |||||
data += 2; | |||||
memcpy(block[i].data, data, block[i].size); | |||||
data += block[i].size / sizeof(uint32_t); | |||||
if (i == 0) { | |||||
// We now know from which mode the data was saved. | |||||
bool currently_spectral = playback_mode_ == PLAYBACK_MODE_SPECTRAL; | |||||
bool requires_spectral = persistent_state_.spectral; | |||||
if (currently_spectral ^ requires_spectral) { | |||||
set_playback_mode(PLAYBACK_MODE_SPECTRAL); | |||||
} | |||||
set_quality(persistent_state_.quality); | |||||
// We can force a switch to this mode, and once everything has been | |||||
// initialized for this mode, we continue with the loop to copy the | |||||
// actual buffer data - with all state variables correctly initialized. | |||||
Prepare(); | |||||
GetPersistentData(block, &num_blocks); | |||||
} | |||||
} | |||||
// We can finally reset the position of the write heads. | |||||
buffer_16_[0].Resync(persistent_state_.write_head[0]); | |||||
buffer_16_[1].Resync(persistent_state_.write_head[1]); | |||||
parameters_.freeze = true; | |||||
silence_ = false; | |||||
return true; | |||||
} | |||||
void GranularProcessor::Prepare() { | |||||
bool playback_mode_changed = previous_playback_mode_ != playback_mode_; | |||||
bool benign_change = false; | |||||
if (!reset_buffers_ && playback_mode_changed && benign_change) { | |||||
ResetFilters(); | |||||
previous_playback_mode_ = playback_mode_; | |||||
} | |||||
if ((playback_mode_changed && !benign_change) || reset_buffers_) { | |||||
parameters_.freeze = false; | |||||
} | |||||
if (reset_buffers_ || (playback_mode_changed && !benign_change)) { | |||||
void* buffer[2]; | |||||
size_t buffer_size[2]; | |||||
void* workspace; | |||||
size_t workspace_size; | |||||
if (num_channels_ == 1) { | |||||
// Large buffer: 120k of sample memory. | |||||
// small buffer: fully allocated to FX workspace. | |||||
buffer[0] = buffer_[0]; | |||||
buffer_size[0] = buffer_size_[0]; | |||||
buffer[1] = NULL; | |||||
buffer_size[1] = 0; | |||||
workspace = buffer_[1]; | |||||
workspace_size = buffer_size_[1]; | |||||
} else { | |||||
// Large buffer: 64k of sample memory + FX workspace. | |||||
// small buffer: 64k of sample memory. | |||||
buffer_size[0] = buffer_size[1] = buffer_size_[1]; | |||||
buffer[0] = buffer_[0]; | |||||
buffer[1] = buffer_[1]; | |||||
workspace_size = buffer_size_[0] - buffer_size_[1]; | |||||
workspace = static_cast<uint8_t*>(buffer[0]) + buffer_size[0]; | |||||
} | |||||
float sr = sample_rate(); | |||||
BufferAllocator allocator(workspace, workspace_size); | |||||
phase_vocoder_.Init( | |||||
buffer, buffer_size, | |||||
lut_sine_window_4096, 4096, | |||||
num_channels_, resolution(), sr); | |||||
reset_buffers_ = false; | |||||
previous_playback_mode_ = playback_mode_; | |||||
} | |||||
phase_vocoder_.Buffer(); | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,206 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Main processing class. | |||||
#ifndef CLOUDS_DSP_GRANULAR_PROCESSOR_H_ | |||||
#define CLOUDS_DSP_GRANULAR_PROCESSOR_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/dsp/filter.h" | |||||
#include "clouds/dsp/correlator.h" | |||||
#include "clouds/dsp/frame.h" | |||||
#include "clouds/dsp/fx/diffuser.h" | |||||
#include "clouds/dsp/fx/pitch_shifter.h" | |||||
#include "clouds/dsp/fx/reverb.h" | |||||
#include "clouds/dsp/granular_processor.h" | |||||
#include "clouds/dsp/granular_sample_player.h" | |||||
#include "clouds/dsp/looping_sample_player.h" | |||||
#include "clouds/dsp/pvoc/phase_vocoder.h" | |||||
#include "clouds/dsp/sample_rate_converter.h" | |||||
#include "clouds/dsp/wsola_sample_player.h" | |||||
namespace clouds { | |||||
const int32_t kDownsamplingFactor = 2; | |||||
enum PlaybackMode { | |||||
PLAYBACK_MODE_GRANULAR, | |||||
PLAYBACK_MODE_STRETCH, | |||||
PLAYBACK_MODE_LOOPING_DELAY, | |||||
PLAYBACK_MODE_SPECTRAL, | |||||
PLAYBACK_MODE_LAST | |||||
}; | |||||
// State of the recording buffer as saved in one of the 4 sample memories. | |||||
struct PersistentState { | |||||
int32_t write_head[2]; | |||||
uint8_t quality; | |||||
uint8_t spectral; | |||||
}; | |||||
// Data block as saved in one of the 4 sample memories. | |||||
struct PersistentBlock { | |||||
uint32_t tag; | |||||
uint32_t size; | |||||
void* data; | |||||
}; | |||||
class GranularProcessor { | |||||
public: | |||||
GranularProcessor() { } | |||||
~GranularProcessor() { } | |||||
void Init( | |||||
void* large_buffer, | |||||
size_t large_buffer_size, | |||||
void* small_buffer, | |||||
size_t small_buffer_size); | |||||
void Process(ShortFrame* input, ShortFrame* output, size_t size); | |||||
void Prepare(); | |||||
inline Parameters* mutable_parameters() { | |||||
return ¶meters_; | |||||
} | |||||
inline const Parameters& parameters() const { | |||||
return parameters_; | |||||
} | |||||
inline void ToggleFreeze() { | |||||
parameters_.freeze = !parameters_.freeze; | |||||
} | |||||
inline void set_freeze(bool freeze) { | |||||
parameters_.freeze = freeze; | |||||
} | |||||
inline bool frozen() const { | |||||
return parameters_.freeze; | |||||
} | |||||
inline void set_silence(bool silence) { | |||||
silence_ = silence; | |||||
} | |||||
inline void set_bypass(bool bypass) { | |||||
bypass_ = bypass; | |||||
} | |||||
inline bool bypass() const { | |||||
return bypass_; | |||||
} | |||||
inline void set_playback_mode(PlaybackMode playback_mode) { | |||||
playback_mode_ = playback_mode; | |||||
} | |||||
inline PlaybackMode playback_mode() const { return playback_mode_; } | |||||
inline void set_quality(int32_t quality) { | |||||
set_num_channels(quality & 1 ? 1 : 2); | |||||
set_low_fidelity(quality >> 1 ? true : false); | |||||
} | |||||
inline void set_num_channels(int32_t num_channels) { | |||||
reset_buffers_ = reset_buffers_ || num_channels_ != num_channels; | |||||
num_channels_ = num_channels; | |||||
} | |||||
inline void set_low_fidelity(bool low_fidelity) { | |||||
reset_buffers_ = reset_buffers_ || low_fidelity != low_fidelity_; | |||||
low_fidelity_ = low_fidelity; | |||||
} | |||||
inline int32_t quality() const { | |||||
int32_t quality = 0; | |||||
if (num_channels_ == 1) quality |= 1; | |||||
if (low_fidelity_) quality |= 2; | |||||
return quality; | |||||
} | |||||
void GetPersistentData(PersistentBlock* block, size_t *num_blocks); | |||||
bool LoadPersistentData(const uint32_t* data); | |||||
void PreparePersistentData(); | |||||
private: | |||||
inline int32_t resolution() const { | |||||
return low_fidelity_ ? 8 : 16; | |||||
} | |||||
inline float sample_rate() const { | |||||
return 32000.0f / \ | |||||
(low_fidelity_ ? kDownsamplingFactor : 1); | |||||
} | |||||
void ResetFilters(); | |||||
void ProcessGranular(FloatFrame* input, FloatFrame* output, size_t size); | |||||
PlaybackMode playback_mode_; | |||||
PlaybackMode previous_playback_mode_; | |||||
int32_t num_channels_; | |||||
bool low_fidelity_; | |||||
bool silence_; | |||||
bool bypass_; | |||||
bool reset_buffers_; | |||||
float freeze_lp_; | |||||
float dry_wet_; | |||||
void* buffer_[2]; | |||||
size_t buffer_size_[2]; | |||||
PhaseVocoder phase_vocoder_; | |||||
stmlib::Svf fb_filter_[2]; | |||||
stmlib::Svf hp_filter_[2]; | |||||
stmlib::Svf lp_filter_[2]; | |||||
AudioBuffer<RESOLUTION_16_BIT> buffer_16_[2]; | |||||
FloatFrame in_[kMaxBlockSize]; | |||||
FloatFrame in_downsampled_[kMaxBlockSize / kDownsamplingFactor]; | |||||
FloatFrame out_downsampled_[kMaxBlockSize / kDownsamplingFactor]; | |||||
FloatFrame out_[kMaxBlockSize]; | |||||
FloatFrame fb_[kMaxBlockSize]; | |||||
int16_t tail_buffer_[2][256]; | |||||
Parameters parameters_; | |||||
SampleRateConverter<-kDownsamplingFactor, 45, src_filter_1x_2_45> src_down_; | |||||
SampleRateConverter<+kDownsamplingFactor, 45, src_filter_1x_2_45> src_up_; | |||||
PersistentState persistent_state_; | |||||
DISALLOW_COPY_AND_ASSIGN(GranularProcessor); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_GRANULAR_PROCESSOR_H_ |
@@ -0,0 +1,256 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Granular playback of audio stored in a buffer. | |||||
#ifndef CLOUDS_DSP_GRANULAR_SAMPLE_PLAYER_H_ | |||||
#define CLOUDS_DSP_GRANULAR_SAMPLE_PLAYER_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <algorithm> | |||||
#include "stmlib/dsp/atan.h" | |||||
#include "stmlib/dsp/units.h" | |||||
#include "stmlib/utils/random.h" | |||||
#include "clouds/dsp/audio_buffer.h" | |||||
#include "clouds/dsp/frame.h" | |||||
#include "clouds/dsp/grain.h" | |||||
#include "clouds/dsp/parameters.h" | |||||
#include "clouds/resources.h" | |||||
namespace clouds { | |||||
const int32_t kMaxNumGrains = 64; | |||||
using namespace stmlib; | |||||
class GranularSamplePlayer { | |||||
public: | |||||
GranularSamplePlayer() { } | |||||
~GranularSamplePlayer() { } | |||||
void Init(int32_t num_channels, int32_t max_num_grains) { | |||||
max_num_grains_ = max_num_grains; | |||||
num_midfi_grains_ = 3 * max_num_grains / 4; | |||||
gain_normalization_ = 1.0f; | |||||
for (int32_t i = 0; i < kMaxNumGrains; ++i) { | |||||
grains_[i].Init(); | |||||
} | |||||
num_grains_ = 0.0f; | |||||
num_channels_ = num_channels; | |||||
grain_size_hint_ = 1024.0f; | |||||
} | |||||
template<Resolution resolution> | |||||
void Play( | |||||
const AudioBuffer<resolution>* buffer, | |||||
const Parameters& parameters, | |||||
float* out, size_t size) { | |||||
float overlap = parameters.granular.overlap; | |||||
overlap = overlap * overlap * overlap; | |||||
float target_num_grains = max_num_grains_ * overlap; | |||||
float p = target_num_grains / static_cast<float>(grain_size_hint_); | |||||
float space_between_grains = grain_size_hint_ / target_num_grains; | |||||
if (parameters.granular.use_deterministic_seed) { | |||||
p = -1.0f; | |||||
} else { | |||||
grain_rate_phasor_ = -1000.0f; | |||||
} | |||||
// Build a list of available grains. | |||||
int32_t num_available_grains = FillAvailableGrainsList(); | |||||
// Try to schedule new grains. | |||||
bool seed_trigger = parameters.trigger; | |||||
for (size_t t = 0; t < size; ++t) { | |||||
grain_rate_phasor_ += 1.0f; | |||||
bool seed_probabilistic = Random::GetFloat() < p | |||||
&& target_num_grains > num_grains_; | |||||
bool seed_deterministic = grain_rate_phasor_ >= space_between_grains; | |||||
bool seed = seed_probabilistic || seed_deterministic || seed_trigger; | |||||
if (num_available_grains && seed) { | |||||
--num_available_grains; | |||||
int32_t index = available_grains_[num_available_grains]; | |||||
GrainQuality quality; | |||||
if (num_available_grains < num_midfi_grains_) { | |||||
quality = GRAIN_QUALITY_MEDIUM; | |||||
} else { | |||||
quality = GRAIN_QUALITY_HIGH; | |||||
} | |||||
Grain* g = &grains_[index]; | |||||
ScheduleGrain( | |||||
g, | |||||
parameters, | |||||
t, | |||||
buffer->size(), | |||||
buffer->head() - size + t, | |||||
quality); | |||||
grain_rate_phasor_ = 0.0f; | |||||
seed_trigger = false; | |||||
} | |||||
} | |||||
// Overlap grains. | |||||
std::fill(&out[0], &out[size * 2], 0.0f); | |||||
float* e = envelope_buffer_; | |||||
for (int32_t i = 0; i < max_num_grains_; ++i) { | |||||
Grain* g = &grains_[i]; | |||||
if (g->recommended_quality() == GRAIN_QUALITY_HIGH) { | |||||
if (num_channels_ == 1) { | |||||
g->OverlapAdd<1, GRAIN_QUALITY_HIGH>(buffer, out, e, size); | |||||
} else { | |||||
g->OverlapAdd<2, GRAIN_QUALITY_HIGH>(buffer, out, e, size); | |||||
} | |||||
} else if (g->recommended_quality() == GRAIN_QUALITY_MEDIUM) { | |||||
if (num_channels_ == 1) { | |||||
g->OverlapAdd<1, GRAIN_QUALITY_MEDIUM>(buffer, out, e, size); | |||||
} else { | |||||
g->OverlapAdd<2, GRAIN_QUALITY_MEDIUM>(buffer, out, e, size); | |||||
} | |||||
} else { | |||||
if (num_channels_ == 1) { | |||||
g->OverlapAdd<1, GRAIN_QUALITY_LOW>(buffer, out, e, size); | |||||
} else { | |||||
g->OverlapAdd<2, GRAIN_QUALITY_LOW>(buffer, out, e, size); | |||||
} | |||||
} | |||||
} | |||||
// Compute normalization factor. | |||||
int32_t active_grains = max_num_grains_ - num_available_grains; | |||||
SLOPE(num_grains_, static_cast<float>(active_grains), 0.9f, 0.2f); | |||||
float gain_normalization = num_grains_ > 2.0f | |||||
? fast_rsqrt_carmack(num_grains_ - 1.0f) | |||||
: 1.0f; | |||||
float window_gain = 1.0f + 2.0f * parameters.granular.window_shape; | |||||
CONSTRAIN(window_gain, 1.0f, 2.0f); | |||||
gain_normalization *= Crossfade( | |||||
1.0f, window_gain, parameters.granular.overlap); | |||||
// Apply gain normalization. | |||||
for (size_t t = 0; t < size; ++t) { | |||||
ONE_POLE(gain_normalization_, gain_normalization, 0.01f) | |||||
*out++ *= gain_normalization_; | |||||
*out++ *= gain_normalization_; | |||||
} | |||||
} | |||||
private: | |||||
int32_t FillAvailableGrainsList() { | |||||
int32_t num_available_grains = 0; | |||||
for (int32_t i = 0; i < max_num_grains_; ++i) { | |||||
if (!grains_[i].active()) { | |||||
available_grains_[num_available_grains] = i; | |||||
++num_available_grains; | |||||
} | |||||
} | |||||
return num_available_grains; | |||||
} | |||||
void ScheduleGrain( | |||||
Grain* grain, | |||||
const Parameters& parameters, | |||||
int32_t pre_delay, | |||||
int32_t buffer_size, | |||||
int32_t buffer_head, | |||||
GrainQuality quality) { | |||||
float position = parameters.position; | |||||
float pitch = parameters.pitch; | |||||
float window_shape = parameters.granular.window_shape; | |||||
float grain_size = Interpolate(lut_grain_size, parameters.size, 256.0f); | |||||
float pitch_ratio = SemitonesToRatio(pitch); | |||||
float inv_pitch_ratio = SemitonesToRatio(-pitch); | |||||
float pan = 0.5f + parameters.stereo_spread * (Random::GetFloat() - 0.5f); | |||||
float gain_l, gain_r; | |||||
if (num_channels_ == 1) { | |||||
gain_l = Interpolate(lut_sin, pan, 256.0f); | |||||
gain_r = Interpolate(lut_sin + 256, pan, 256.0f); | |||||
} else { | |||||
if (pan < 0.5f) { | |||||
gain_l = 1.0f; | |||||
gain_r = 2.0f * pan; | |||||
} else { | |||||
gain_r = 1.0f; | |||||
gain_l = 2.0f * (1.0f - pan); | |||||
} | |||||
} | |||||
if (pitch_ratio > 1.0f) { | |||||
// The grain's play-head moves faster than the buffer record-head. | |||||
// we must make sure that the grain will not consume too much data. | |||||
// In some situations, it might be necessary to reduce the size of the | |||||
// grain. | |||||
grain_size = std::min(grain_size, buffer_size * 0.25f * inv_pitch_ratio); | |||||
} | |||||
float eaten_by_play_head = grain_size * pitch_ratio; | |||||
float eaten_by_recording_head = grain_size; | |||||
float available = 0.0; | |||||
available += static_cast<float>(buffer_size); | |||||
available -= eaten_by_play_head; | |||||
available -= eaten_by_recording_head; | |||||
int32_t size = static_cast<int32_t>(grain_size) & ~1; | |||||
int32_t start = buffer_head - static_cast<int32_t>( | |||||
position * available + eaten_by_play_head); | |||||
grain->Start( | |||||
pre_delay, | |||||
buffer_size, | |||||
start, | |||||
size, | |||||
static_cast<uint32_t>(pitch_ratio * 65536.0f), | |||||
window_shape, | |||||
gain_l, | |||||
gain_r, | |||||
quality); | |||||
ONE_POLE(grain_size_hint_, grain_size, 0.1f); | |||||
} | |||||
int32_t max_num_grains_; | |||||
int32_t num_midfi_grains_; | |||||
int32_t num_channels_; | |||||
float num_grains_; | |||||
float gain_normalization_; | |||||
float grain_size_hint_; | |||||
float grain_rate_phasor_; | |||||
Grain grains_[kMaxNumGrains]; | |||||
int32_t available_grains_[kMaxNumGrains]; | |||||
float envelope_buffer_[kMaxBlockSize]; | |||||
DISALLOW_COPY_AND_ASSIGN(GranularSamplePlayer); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_GRANULAR_SAMPLE_PLAYER_H_ |
@@ -0,0 +1,206 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Naive playback of audio stored in a buffer. | |||||
#ifndef CLOUDS_DSP_LOOPING_SAMPLE_PLAYER_H_ | |||||
#define CLOUDS_DSP_LOOPING_SAMPLE_PLAYER_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <algorithm> | |||||
#include "stmlib/dsp/units.h" | |||||
#include "clouds/dsp/audio_buffer.h" | |||||
#include "clouds/dsp/frame.h" | |||||
#include "clouds/dsp/parameters.h" | |||||
#include "clouds/resources.h" | |||||
namespace clouds { | |||||
const float kCrossfadeDuration = 64.0f; | |||||
using namespace stmlib; | |||||
class LoopingSamplePlayer { | |||||
public: | |||||
LoopingSamplePlayer() { } | |||||
~LoopingSamplePlayer() { } | |||||
void Init(int32_t num_channels) { | |||||
num_channels_ = num_channels; | |||||
phase_ = 0.0f; | |||||
current_delay_ = 0.0f; | |||||
loop_point_ = 0.0f; | |||||
loop_duration_ = 0.0f; | |||||
tap_delay_ = 0; | |||||
tap_delay_counter_ = 0; | |||||
synchronized_ = false; | |||||
tail_duration_ = 1.0f; | |||||
} | |||||
inline bool synchronized() const { return synchronized_; } | |||||
template<Resolution resolution> | |||||
void Play( | |||||
const AudioBuffer<resolution>* buffer, | |||||
const Parameters& parameters, | |||||
float* out, size_t size) { | |||||
int32_t max_delay = buffer->size() - kCrossfadeDuration; | |||||
tap_delay_counter_ += size; | |||||
if (tap_delay_counter_ > max_delay) { | |||||
tap_delay_ = 0; | |||||
tap_delay_counter_ = 0; | |||||
synchronized_ = false; | |||||
} | |||||
if (parameters.trigger) { | |||||
tap_delay_ = tap_delay_counter_; | |||||
tap_delay_counter_ = 0; | |||||
synchronized_ = tap_delay_ > 128; | |||||
loop_reset_ = phase_; | |||||
phase_ = 0.0f; | |||||
} | |||||
if (!parameters.freeze) { | |||||
while (size--) { | |||||
float target_delay = parameters.position * max_delay; | |||||
if (synchronized_) { | |||||
target_delay = tap_delay_; | |||||
} | |||||
float error = (target_delay - current_delay_); | |||||
float delay = current_delay_ + 0.00005f * error; | |||||
current_delay_ = delay; | |||||
int32_t delay_int = (buffer->head() - 4 - size + buffer->size()) << 12; | |||||
delay_int -= static_cast<int32_t>(delay * 4096.0f); | |||||
float l = buffer[0].ReadHermite((delay_int >> 12), delay_int << 4); | |||||
if (num_channels_ == 1) { | |||||
*out++ = l; | |||||
*out++ = l; | |||||
} else if (num_channels_ == 2) { | |||||
float r = buffer[1].ReadHermite((delay_int >> 12), delay_int << 4); | |||||
*out++ = l; | |||||
*out++ = r; | |||||
} | |||||
} | |||||
phase_ = 0.0f; | |||||
} else { | |||||
float loop_point = parameters.position * max_delay * 15.0f / 16.0f; | |||||
loop_point += kCrossfadeDuration; | |||||
float d = parameters.size; | |||||
float loop_duration = (0.01f + 0.99f * d * d * d) * max_delay; | |||||
if (synchronized_) { | |||||
loop_duration = tap_delay_; | |||||
} | |||||
if (loop_point + loop_duration >= max_delay) { | |||||
loop_point = max_delay - loop_duration; | |||||
} | |||||
float phase_increment = synchronized_ | |||||
? 1.0f | |||||
: SemitonesToRatio(parameters.pitch); | |||||
while (size--) { | |||||
if (phase_ >= loop_duration_ || phase_ == 0.0f) { | |||||
if (phase_ >= loop_duration_) { | |||||
loop_reset_ = loop_duration_; | |||||
} | |||||
if (loop_reset_ >= loop_duration_) { | |||||
loop_reset_ = loop_duration_; | |||||
} | |||||
tail_start_ = loop_duration_ - loop_reset_ + loop_point_; | |||||
phase_ = 0.0f; | |||||
tail_duration_ = std::min( | |||||
kCrossfadeDuration, | |||||
kCrossfadeDuration * phase_increment); | |||||
loop_point_ = loop_point; | |||||
loop_duration_ = loop_duration; | |||||
} | |||||
phase_ += phase_increment; | |||||
float gain = 1.0f; | |||||
if (tail_duration_ != 0.0f) { | |||||
gain = phase_ / tail_duration_; | |||||
CONSTRAIN(gain, 0.0f, 1.0f); | |||||
} | |||||
int32_t delay_int = (buffer->head() - 4 + buffer->size()) << 12; | |||||
int32_t position = delay_int - static_cast<int32_t>( | |||||
(loop_duration_ - phase_ + loop_point_) * 4096.0f); | |||||
float l = buffer[0].ReadHermite((position >> 12), position << 4); | |||||
if (num_channels_ == 1) { | |||||
out[0] = l * gain; | |||||
out[1] = l * gain; | |||||
} else if (num_channels_ == 2) { | |||||
float r = buffer[1].ReadHermite((position >> 12), position << 4); | |||||
out[0] = l * gain; | |||||
out[1] = r * gain; | |||||
} | |||||
if (gain != 1.0f) { | |||||
gain = 1.0f - gain; | |||||
int32_t position = delay_int - static_cast<int32_t>( | |||||
(-phase_ + tail_start_) * 4096.0f); | |||||
float l = buffer[0].ReadHermite((position >> 12), position << 4); | |||||
if (num_channels_ == 1) { | |||||
out[0] += l * gain; | |||||
out[1] += l * gain; | |||||
} else if (num_channels_ == 2) { | |||||
float r = buffer[1].ReadHermite((position >> 12), position << 4); | |||||
out[0] += l * gain; | |||||
out[1] += r * gain; | |||||
} | |||||
} | |||||
out += 2; | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
float phase_; | |||||
float current_delay_; | |||||
float loop_point_; | |||||
float loop_duration_; | |||||
float tail_start_; | |||||
float tail_duration_; | |||||
float loop_reset_; | |||||
bool synchronized_; | |||||
int32_t num_channels_; | |||||
int32_t elapsed_; | |||||
int32_t tap_delay_; | |||||
int32_t tap_delay_counter_; | |||||
DISALLOW_COPY_AND_ASSIGN(LoopingSamplePlayer); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_LOOPING_SAMPLE_PLAYER_H_ |
@@ -0,0 +1,69 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Mu-law encoding. | |||||
#include "clouds/dsp/mu_law.h" | |||||
namespace clouds { | |||||
/* extern */ | |||||
int16_t lut_ulaw[256] = { | |||||
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, | |||||
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, | |||||
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, | |||||
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, | |||||
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, | |||||
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, | |||||
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, | |||||
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, | |||||
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, | |||||
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924, | |||||
-876, -844, -812, -780, -748, -716, -684, -652, | |||||
-620, -588, -556, -524, -492, -460, -428, -396, | |||||
-372, -356, -340, -324, -308, -292, -276, -260, | |||||
-244, -228, -212, -196, -180, -164, -148, -132, | |||||
-120, -112, -104, -96, -88, -80, -72, -64, | |||||
-56, -48, -40, -32, -24, -16, -8, 0, | |||||
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, | |||||
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, | |||||
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, | |||||
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, | |||||
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, | |||||
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, | |||||
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, | |||||
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, | |||||
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, | |||||
1372, 1308, 1244, 1180, 1116, 1052, 988, 924, | |||||
876, 844, 812, 780, 748, 716, 684, 652, | |||||
620, 588, 556, 524, 492, 460, 428, 396, | |||||
372, 356, 340, 324, 308, 292, 276, 260, | |||||
244, 228, 212, 196, 180, 164, 148, 132, | |||||
120, 112, 104, 96, 88, 80, 72, 64, | |||||
56, 48, 40, 32, 24, 16, 8, 0 | |||||
}; | |||||
} // namespace clouds |
@@ -0,0 +1,83 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Mu-law encoding. | |||||
#ifndef CLOUDS_DSP_MU_LAW_H_ | |||||
#define CLOUDS_DSP_MU_LAW_H_ | |||||
#include "stmlib/stmlib.h" | |||||
namespace clouds { | |||||
// inline short MuLaw2Lin(uint8_t u_val) { | |||||
// int16_t t; | |||||
// u_val = ~u_val; | |||||
// t = ((u_val & 0xf) << 3) + 0x84; | |||||
// t <<= ((unsigned)u_val & 0x70) >> 4; | |||||
// return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84)); | |||||
// } | |||||
extern int16_t lut_ulaw[256]; | |||||
inline short MuLaw2Lin(uint8_t u_val) { | |||||
return lut_ulaw[u_val]; | |||||
} | |||||
inline unsigned char Lin2MuLaw(int16_t pcm_val) { | |||||
int16_t mask; | |||||
int16_t seg; | |||||
uint8_t uval; | |||||
pcm_val = pcm_val >> 2; | |||||
if (pcm_val < 0) { | |||||
pcm_val = -pcm_val; | |||||
mask = 0x7f; | |||||
} else { | |||||
mask = 0xff; | |||||
} | |||||
if (pcm_val > 8159) pcm_val = 8159; | |||||
pcm_val += (0x84 >> 2); | |||||
if (pcm_val <= 0x3f) seg = 0; | |||||
else if (pcm_val <= 0x7f) seg = 1; | |||||
else if (pcm_val <= 0xff) seg = 2; | |||||
else if (pcm_val <= 0x1ff) seg = 3; | |||||
else if (pcm_val <= 0x3ff) seg = 4; | |||||
else if (pcm_val <= 0x7ff) seg = 5; | |||||
else if (pcm_val <= 0xfff) seg = 6; | |||||
else if (pcm_val <= 0x1fff) seg = 7; | |||||
else seg = 8; | |||||
if (seg >= 8) | |||||
return static_cast<uint8_t>(0x7f ^ mask); | |||||
else { | |||||
uval = static_cast<uint8_t>((seg << 4) | ((pcm_val >> (seg + 1)) & 0x0f)); | |||||
return (uval ^ mask); | |||||
} | |||||
} | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_MU_LAW_H_ |
@@ -0,0 +1,68 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Parameters of the granular effect. | |||||
#ifndef CLOUDS_DSP_PARAMETERS_H_ | |||||
#define CLOUDS_DSP_PARAMETERS_H_ | |||||
#include "stmlib/stmlib.h" | |||||
namespace clouds { | |||||
struct Parameters { | |||||
float position; | |||||
float size; | |||||
float pitch; | |||||
float density; | |||||
float texture; | |||||
float dry_wet; | |||||
float stereo_spread; | |||||
float feedback; | |||||
float reverb; | |||||
bool freeze; | |||||
bool trigger; | |||||
bool gate; | |||||
struct Granular { | |||||
float overlap; | |||||
float window_shape; | |||||
float stereo_spread; | |||||
bool use_deterministic_seed; | |||||
} granular; | |||||
struct Spectral { | |||||
float quantization; | |||||
float refresh_rate; | |||||
float phase_randomization; | |||||
float warp; | |||||
} spectral; | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_PARAMETERS_H_ |
@@ -0,0 +1,361 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Transformations applied to a single STFT slice. | |||||
#include "clouds/dsp/pvoc/frame_transformation.h" | |||||
#include <algorithm> | |||||
#include "stmlib/dsp/atan.h" | |||||
#include "stmlib/dsp/units.h" | |||||
#include "stmlib/utils/random.h" | |||||
#include "clouds/dsp/frame.h" | |||||
#include "clouds/dsp/parameters.h" | |||||
namespace clouds { | |||||
using namespace std; | |||||
using namespace stmlib; | |||||
void FrameTransformation::Init( | |||||
float* buffer, | |||||
int32_t fft_size, | |||||
int32_t num_textures) { | |||||
fft_size_ = fft_size; | |||||
size_ = (fft_size >> 1) - kHighFrequencyTruncation; | |||||
for (int32_t i = 0; i < num_textures; ++i) { | |||||
textures_[i] = &buffer[i * size_]; | |||||
} | |||||
phases_ = static_cast<uint16_t*>((void*)(textures_[num_textures - 1])); | |||||
num_textures_ = num_textures - 1; // Last texture is used for storing phases. | |||||
phases_delta_ = phases_ + size_; | |||||
glitch_algorithm_ = 0; | |||||
Reset(); | |||||
} | |||||
void FrameTransformation::Reset() { | |||||
for (int32_t i = 0; i < num_textures_; ++i) { | |||||
fill(&textures_[i][0], &textures_[i][size_], 0.0f); | |||||
} | |||||
} | |||||
void FrameTransformation::Process( | |||||
const Parameters& parameters, | |||||
float* fft_out, | |||||
float* ifft_in) { | |||||
fft_out[0] = 0.0f; | |||||
fft_out[fft_size_ >> 1] = 0.0f; | |||||
bool freeze = parameters.freeze; | |||||
bool glitch = parameters.gate; | |||||
float pitch_ratio = SemitonesToRatio(parameters.pitch); | |||||
if (!freeze) { | |||||
RectangularToPolar(fft_out); | |||||
StoreMagnitudes( | |||||
fft_out, | |||||
parameters.position, | |||||
parameters.spectral.refresh_rate); | |||||
} | |||||
float* temp = &fft_out[0]; | |||||
ReplayMagnitudes(ifft_in, parameters.position); | |||||
WarpMagnitudes(ifft_in, temp, parameters.spectral.warp); | |||||
ShiftMagnitudes(temp, ifft_in, pitch_ratio); | |||||
if (glitch) { | |||||
AddGlitch(ifft_in); | |||||
} | |||||
QuantizeMagnitudes(ifft_in, parameters.spectral.quantization); | |||||
SetPhases(ifft_in, parameters.spectral.phase_randomization, pitch_ratio); | |||||
PolarToRectangular(ifft_in); | |||||
if (!glitch) { | |||||
// Decide on which glitch algorithm will be used next time... if glitch | |||||
// is enabled on the next frame! | |||||
glitch_algorithm_ = stmlib::Random::GetSample() & 3; | |||||
} | |||||
ifft_in[0] = 0.0f; | |||||
ifft_in[fft_size_ >> 1] = 0.0f; | |||||
} | |||||
void FrameTransformation::RectangularToPolar(float* fft_data) { | |||||
float* real = &fft_data[0]; | |||||
float* imag = &fft_data[fft_size_ >> 1]; | |||||
float* magnitude = &fft_data[0]; | |||||
for (int32_t i = 1; i < size_; ++i) { | |||||
uint16_t angle = fast_atan2r(imag[i], real[i], &magnitude[i]); | |||||
phases_delta_[i] = angle - phases_[i]; | |||||
phases_[i] = angle; | |||||
} | |||||
} | |||||
void FrameTransformation::SetPhases( | |||||
float* destination, | |||||
float phase_randomization, | |||||
float pitch_ratio) { | |||||
uint32_t* synthesis_phase = (uint32_t*) &destination[fft_size_ >> 1]; | |||||
for (int32_t i = 0; i < size_; ++i) { | |||||
synthesis_phase[i] = phases_[i]; | |||||
phases_[i] += static_cast<uint16_t>( | |||||
static_cast<float>(phases_delta_[i]) * pitch_ratio); | |||||
} | |||||
float r = phase_randomization; | |||||
r = (r - 0.05f) * 1.06f; | |||||
CONSTRAIN(r, 0.0f, 1.0f); | |||||
r *= r; | |||||
int32_t amount = static_cast<int32_t>(r * 32768.0f); | |||||
for (int32_t i = 0; i < size_; ++i) { | |||||
synthesis_phase[i] += \ | |||||
static_cast<int32_t>(stmlib::Random::GetSample()) * amount >> 14; | |||||
} | |||||
} | |||||
void FrameTransformation::PolarToRectangular(float* fft_data) { | |||||
float* real = &fft_data[0]; | |||||
float* imag = &fft_data[fft_size_ >> 1]; | |||||
float* magnitude = &fft_data[0]; | |||||
uint32_t* angle = (uint32_t*) &fft_data[fft_size_ >> 1]; | |||||
for (int32_t i = 1; i < size_; ++i) { | |||||
fast_p2r(magnitude[i], angle[i], &real[i], &imag[i]); | |||||
} | |||||
for (int32_t i = size_; i < fft_size_ >> 1; ++i) { | |||||
real[i] = imag[i] = 0.0f; | |||||
} | |||||
} | |||||
void FrameTransformation::AddGlitch(float* xf_polar) { | |||||
float* x = xf_polar; | |||||
switch (glitch_algorithm_) { | |||||
case 0: | |||||
// Spectral hold and blow. | |||||
{ | |||||
// Create trails | |||||
float held = 0.0; | |||||
for (int32_t i = 0; i < size_; ++i) { | |||||
if ((stmlib::Random::GetSample() & 15) == 0) { | |||||
held = x[i]; | |||||
} | |||||
x[i] = held; | |||||
held = held * 1.01f; | |||||
} | |||||
} | |||||
break; | |||||
case 1: | |||||
// Spectral shift up with aliasing. | |||||
{ | |||||
float factor = 1.0f + (stmlib::Random::GetSample() & 7) / 4.0f; | |||||
float source = 0.0f; | |||||
for (int32_t i = 0; i < size_; ++i) { | |||||
source += factor; | |||||
if (source >= size_) { | |||||
source = 0.0f; | |||||
} | |||||
x[i] = x[static_cast<int32_t>(source)]; | |||||
} | |||||
} | |||||
break; | |||||
case 2: | |||||
// Kill largest harmonic and boost second largest. | |||||
*std::max_element(&x[0], &x[size_]) = 0.0f; | |||||
*std::max_element(&x[0], &x[size_]) *= 8.0f; | |||||
break; | |||||
case 3: | |||||
{ | |||||
// Nasty high-pass | |||||
for (int32_t i = 0; i < size_; ++i) { | |||||
uint32_t random = stmlib::Random::GetSample() & 15; | |||||
if (random == 0) { | |||||
x[i] *= static_cast<float>(i) / 16.0f; | |||||
} | |||||
} | |||||
} | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
void FrameTransformation::QuantizeMagnitudes(float* xf_polar, float amount) { | |||||
if (amount <= 0.48f) { | |||||
amount = amount * 2.0f; | |||||
float scale_down = 0.5f * SemitonesToRatio( | |||||
-108.0f * (1.0f - amount * amount)) / float(fft_size_); | |||||
float scale_up = 1.0f / scale_down; | |||||
for (int32_t i = 0.0f; i < size_; ++i) { | |||||
xf_polar[i] = scale_up * static_cast<float>( | |||||
static_cast<int32_t>(scale_down * xf_polar[i])); | |||||
} | |||||
} else if (amount >= 0.52f) { | |||||
amount = (amount - 0.52f) * 2.0f; | |||||
float norm = *std::max_element(&xf_polar[0], &xf_polar[size_]); | |||||
float inv_norm = 1.0f / (norm + 0.0001f); | |||||
for (int32_t i = 1.0f; i < size_; ++i) { | |||||
float x = xf_polar[i] * inv_norm; | |||||
float warped = 4.0f * x * (1.0f - x) * (1.0f - x) * (1.0f - x); | |||||
xf_polar[i] = (x + (warped - x) * amount) * norm; | |||||
} | |||||
} | |||||
} | |||||
const float kWarpPolynomials[6][4] = { | |||||
{ 10.5882f, -14.8824f, 5.29412f, 0.0f }, | |||||
{ -7.3333f, +9.0, -1.79167f, 0.125f }, | |||||
{ 0.0f, 0.0f, 1.0f, 0.0f }, | |||||
{ 0.0f, 0.5f, 0.5f, 0.0f }, | |||||
{ -7.3333f, +9.5f, -2.416667f, 0.25f }, | |||||
{ -7.3333f, +9.5f, -2.416667f, 0.25f }, | |||||
}; | |||||
void FrameTransformation::WarpMagnitudes( | |||||
float* source, | |||||
float* xf_polar, | |||||
float amount) { | |||||
float bin_width = 1.0f / static_cast<float>(size_); | |||||
float f = 0.0; | |||||
float coefficients[4]; | |||||
amount *= 4.0f; | |||||
MAKE_INTEGRAL_FRACTIONAL(amount); | |||||
for (int32_t i = 0; i < 4; ++i) { | |||||
coefficients[i] = Crossfade( | |||||
kWarpPolynomials[amount_integral][i], | |||||
kWarpPolynomials[amount_integral + 1][i], | |||||
amount_fractional); | |||||
} | |||||
float a = coefficients[0]; | |||||
float b = coefficients[1]; | |||||
float c = coefficients[2]; | |||||
float d = coefficients[3]; | |||||
for (int32_t i = 1.0f; i < size_; ++i) { | |||||
f += bin_width; | |||||
float wf = (d + f * (c + f * (b + a * f))) * size_; | |||||
xf_polar[i] = Interpolate(source, wf, 1.0f); | |||||
} | |||||
} | |||||
void FrameTransformation::ShiftMagnitudes( | |||||
float* source, | |||||
float* xf_polar, | |||||
float pitch_ratio) { | |||||
float* destination = &xf_polar[0]; | |||||
float* temp = &xf_polar[size_]; | |||||
if (pitch_ratio == 1.0f) { | |||||
copy(&source[0], &source[size_], &temp[0]); | |||||
} else if (pitch_ratio > 1.0f) { | |||||
float index = 1.0f; | |||||
float increment = 1.0f / pitch_ratio; | |||||
for (int32_t i = 1; i < size_; ++i) { | |||||
temp[i] = Interpolate(source, index, 1.0f); | |||||
index += increment; | |||||
} | |||||
} else { | |||||
fill(&temp[0], &temp[size_], 0.0f); | |||||
float index = 1.0f; | |||||
float increment = pitch_ratio; | |||||
for (int32_t i = 1; i < size_; ++i) { | |||||
MAKE_INTEGRAL_FRACTIONAL(index) | |||||
temp[index_integral] += (1.0f - index_fractional) * source[i]; | |||||
temp[index_integral + 1] += index_fractional * source[i]; | |||||
index += increment; | |||||
} | |||||
} | |||||
copy(&temp[0], &temp[size_], &destination[0]); | |||||
} | |||||
void FrameTransformation::StoreMagnitudes( | |||||
float* xf_polar, | |||||
float position, | |||||
float feedback) { | |||||
// Write into magnitude buffers. | |||||
float index_float = position * float(num_textures_ - 1); | |||||
int32_t index_int = static_cast<int32_t>(index_float); | |||||
float index_fractional = index_float - index_int; | |||||
float gain_a = 1.0f - index_fractional; | |||||
float gain_b = index_fractional; | |||||
float* a = textures_[index_int]; | |||||
float* b = textures_[index_int + (position == 1.0f ? 0 : 1)]; | |||||
if (feedback >= 0.5f) { | |||||
feedback = 2.0f * (feedback - 0.5f); | |||||
if (feedback < 0.5f) { | |||||
gain_a *= 1.0f - feedback; | |||||
gain_b *= 1.0f - feedback; | |||||
for (int32_t i = 0; i < size_; ++i) { | |||||
float x = *xf_polar++; | |||||
a[i] = Crossfade(a[i], x, gain_a); | |||||
b[i] = Crossfade(b[i], x, gain_b); | |||||
} | |||||
} else { | |||||
float t = (feedback - 0.5f) * 0.7f + 0.5f; | |||||
float gain_new = t - 0.5f; | |||||
gain_new = gain_new * gain_new * 2.0f + 0.5f; | |||||
float gain_new_a = gain_a * gain_new; | |||||
float gain_new_b = gain_b * gain_new; | |||||
float gain_old_a = 1.0f - gain_a * (1.0f - t); | |||||
float gain_old_b = 1.0f - gain_b * (1.0f - t); | |||||
for (int32_t i = 0; i < size_; ++i) { | |||||
float x = *xf_polar++; | |||||
a[i] = a[i] * gain_old_a + x * gain_new_a; | |||||
b[i] = b[i] * gain_old_b + x * gain_new_b; | |||||
} | |||||
} | |||||
} else { | |||||
feedback *= 2.0f; | |||||
feedback *= feedback; | |||||
uint16_t threshold = feedback * 65535.0f; | |||||
for (int32_t i = 0; i < size_; ++i) { | |||||
float x = *xf_polar++; | |||||
float gain = static_cast<uint16_t>(Random::GetSample()) <= threshold | |||||
? 1.0f : 0.0f; | |||||
a[i] = Crossfade(a[i], x, gain_a * gain); | |||||
b[i] = Crossfade(b[i], x, gain_b * gain); | |||||
} | |||||
} | |||||
} | |||||
void FrameTransformation::ReplayMagnitudes(float* xf_polar, float position) { | |||||
float index_float = position * float(num_textures_ - 1); | |||||
int32_t index_int = static_cast<int32_t>(index_float); | |||||
float index_fractional = index_float - static_cast<float>(index_int); | |||||
float* a = textures_[index_int]; | |||||
float* b = textures_[index_int + (position == 1.0f ? 0 : 1)]; | |||||
for (int32_t i = 0; i < size_; ++i) { | |||||
xf_polar[i] = Crossfade(a[i], b[i], index_fractional); | |||||
} | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,100 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Transformations applied to a single STFT slice. | |||||
#ifndef CLOUDS_DSP_PVOC_FRAME_TRANSFORMATION_H_ | |||||
#define CLOUDS_DSP_PVOC_FRAME_TRANSFORMATION_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "clouds/dsp/pvoc/stft.h" | |||||
#include "clouds/resources.h" | |||||
namespace clouds { | |||||
const int32_t kMaxNumTextures = 7; | |||||
const int32_t kHighFrequencyTruncation = 16; | |||||
struct Parameters; | |||||
class FrameTransformation { | |||||
public: | |||||
FrameTransformation() { } | |||||
~FrameTransformation() { } | |||||
void Init(float* buffer, int32_t fft_size, int32_t num_textures); | |||||
void Reset(); | |||||
void Process( | |||||
const Parameters& parameters, | |||||
float* fft_out, | |||||
float* ifft_in); | |||||
private: | |||||
void RectangularToPolar(float* fft_data); | |||||
void PolarToRectangular(float* fft_data); | |||||
void AddGlitch(float* xf_polar); | |||||
void ShiftMagnitudes( | |||||
float* source, | |||||
float* xf_polar, | |||||
float pitch_ratio); | |||||
void WarpMagnitudes( | |||||
float* source, | |||||
float* xf_polar, | |||||
float amount); | |||||
void QuantizeMagnitudes(float* xf_polar, float amount); | |||||
void StoreMagnitudes(float* xf_polar, float position, float feedback); | |||||
void SetPhases(float* destination, float diffusion, float pitch_ratio); | |||||
void ReplayMagnitudes(float* xf_polar, float position); | |||||
void DiffuseMagnitudes(float* xf_polar, float diffusion); | |||||
inline void fast_p2r(float magnitude, uint16_t angle, float* re, float* im) { | |||||
angle >>= 6; | |||||
*re = magnitude * lut_sin[angle + 256]; | |||||
*im = magnitude * lut_sin[angle]; | |||||
} | |||||
int32_t fft_size_; | |||||
int32_t num_textures_; | |||||
int32_t size_; | |||||
// Magnitude buffers. | |||||
float* textures_[kMaxNumTextures]; | |||||
// Original phase and phase unrolling buffers. | |||||
uint16_t* phases_; | |||||
uint16_t* phases_delta_; | |||||
int8_t glitch_algorithm_; | |||||
DISALLOW_COPY_AND_ASSIGN(FrameTransformation); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_PVOC_FRAME_TRANSFORMATION_H_ |
@@ -0,0 +1,107 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Naive phase vocoder. | |||||
#include "clouds/dsp/pvoc/phase_vocoder.h" | |||||
#include <algorithm> | |||||
#include "stmlib/utils/buffer_allocator.h" | |||||
namespace clouds { | |||||
using namespace std; | |||||
using namespace stmlib; | |||||
void PhaseVocoder::Init( | |||||
void** buffer, | |||||
size_t* buffer_size, | |||||
const float* large_window_lut, | |||||
size_t largest_fft_size, | |||||
int32_t num_channels, | |||||
int32_t resolution, | |||||
float sample_rate) { | |||||
num_channels_ = num_channels; | |||||
size_t fft_size = largest_fft_size; | |||||
size_t hop_ratio = 4; | |||||
BufferAllocator allocator_0(buffer[0], buffer_size[0]); | |||||
BufferAllocator allocator_1(buffer[1], buffer_size[1]); | |||||
BufferAllocator* allocator[2] = { &allocator_0, &allocator_1 }; | |||||
float* fft_buffer = allocator[0]->Allocate<float>(fft_size); | |||||
float* ifft_buffer = allocator[num_channels_ - 1]->Allocate<float>(fft_size); | |||||
size_t num_textures = kMaxNumTextures; | |||||
size_t texture_size = (fft_size >> 1) - kHighFrequencyTruncation; | |||||
for (int32_t i = 0; i < num_channels_; ++i) { | |||||
short* ana_syn_buffer = allocator[i]->Allocate<short>( | |||||
(fft_size + (fft_size >> 1)) * 2); | |||||
num_textures = min( | |||||
allocator[i]->free() / (sizeof(float) * texture_size), | |||||
num_textures); | |||||
stft_[i].Init( | |||||
&fft_, | |||||
fft_size, | |||||
fft_size / hop_ratio, | |||||
fft_buffer, | |||||
ifft_buffer, | |||||
large_window_lut, | |||||
ana_syn_buffer, | |||||
&frame_transformation_[i]); | |||||
} | |||||
for (int32_t i = 0; i < num_channels_; ++i) { | |||||
float* texture_buffer = allocator[i]->Allocate<float>( | |||||
num_textures * texture_size); | |||||
frame_transformation_[i].Init(texture_buffer, fft_size, num_textures); | |||||
} | |||||
} | |||||
void PhaseVocoder::Process( | |||||
const Parameters& parameters, | |||||
const FloatFrame* input, | |||||
FloatFrame* output, size_t size) { | |||||
const float* input_samples = &input[0].l; | |||||
float* output_samples = &output[0].l; | |||||
for (int32_t i = 0; i < num_channels_; ++i) { | |||||
stft_[i].Process( | |||||
parameters, | |||||
input_samples + i, | |||||
output_samples + i, | |||||
size, | |||||
2); | |||||
} | |||||
} | |||||
void PhaseVocoder::Buffer() { | |||||
for (int32_t i = 0; i < num_channels_; ++i) { | |||||
stft_[i].Buffer(); | |||||
} | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,76 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Naive phase vocoder. | |||||
#ifndef CLOUDS_DSP_PVOC_PHASE_VOCODER_H_ | |||||
#define CLOUDS_DSP_PVOC_PHASE_VOCODER_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/fft/shy_fft.h" | |||||
#include "clouds/dsp/frame.h" | |||||
#include "clouds/dsp/pvoc/stft.h" | |||||
#include "clouds/dsp/pvoc/frame_transformation.h" | |||||
namespace clouds { | |||||
struct Parameters; | |||||
class PhaseVocoder { | |||||
public: | |||||
PhaseVocoder() { } | |||||
~PhaseVocoder() { } | |||||
void Init( | |||||
void** buffer, size_t* buffer_size, | |||||
const float* large_window_lut, size_t largest_fft_size, | |||||
int32_t num_channels, | |||||
int32_t resolution, | |||||
float sample_rate); | |||||
void Process( | |||||
const Parameters& parameters, | |||||
const FloatFrame* input, | |||||
FloatFrame* output, | |||||
size_t size); | |||||
void Buffer(); | |||||
private: | |||||
FFT fft_; | |||||
STFT stft_[2]; | |||||
FrameTransformation frame_transformation_[2]; | |||||
int32_t num_channels_; | |||||
DISALLOW_COPY_AND_ASSIGN(PhaseVocoder); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_PVOC_PHASE_VOCODER_H_ |
@@ -0,0 +1,181 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// STFT with overlap-add. | |||||
#include "clouds/dsp/pvoc/stft.h" | |||||
#include <algorithm> | |||||
#include "clouds/dsp/pvoc/frame_transformation.h" | |||||
#include "stmlib/dsp/dsp.h" | |||||
namespace clouds { | |||||
using namespace std; | |||||
using namespace stmlib; | |||||
void STFT::Init( | |||||
FFT* fft, | |||||
size_t fft_size, | |||||
size_t hop_size, | |||||
float* fft_buffer, | |||||
float* ifft_buffer, | |||||
const float* window_lut, | |||||
short* analysis_synthesis_buffer, | |||||
Modifier* modifier) { | |||||
fft_size_ = fft_size; | |||||
hop_size_ = hop_size; | |||||
fft_num_passes_ = 0; | |||||
for (size_t t = fft_size; t > 1; t >>= 1) { | |||||
++fft_num_passes_; | |||||
} | |||||
buffer_size_ = fft_size_ + hop_size_; | |||||
fft_ = fft; | |||||
fft_->Init(); | |||||
analysis_ = &analysis_synthesis_buffer[0]; | |||||
synthesis_ = &analysis_synthesis_buffer[buffer_size_]; | |||||
ifft_in_ = fft_in_ = fft_buffer; | |||||
ifft_out_ = fft_out_ = ifft_buffer; | |||||
window_ = window_lut; | |||||
window_stride_ = LUT_SINE_WINDOW_4096_SIZE / fft_size; | |||||
modifier_ = modifier; | |||||
parameters_ = NULL; | |||||
Reset(); | |||||
} | |||||
void STFT::Reset() { | |||||
buffer_ptr_ = 0; | |||||
process_ptr_ = (2 * hop_size_) % buffer_size_; | |||||
block_size_ = 0; | |||||
fill(&analysis_[0], &analysis_[buffer_size_], 0); | |||||
fill(&synthesis_[0], &synthesis_[buffer_size_], 0); | |||||
ready_ = 0; | |||||
done_ = 0; | |||||
} | |||||
void STFT::Process( | |||||
const Parameters& parameters, | |||||
const float* input, | |||||
float* output, | |||||
size_t size, | |||||
size_t stride) { | |||||
parameters_ = ¶meters; | |||||
while (size) { | |||||
size_t processed = min(size, hop_size_ - block_size_); | |||||
for (size_t i = 0; i < processed; ++i) { | |||||
int32_t sample = *input * 32768.0f; | |||||
analysis_[buffer_ptr_ + i] = Clip16(sample); | |||||
*output = static_cast<float>(synthesis_[buffer_ptr_ + i]) / 16384.0f; | |||||
input += stride; | |||||
output += stride; | |||||
} | |||||
block_size_ += processed; | |||||
size -= processed; | |||||
buffer_ptr_ += processed; | |||||
if (buffer_ptr_ >= buffer_size_) { | |||||
buffer_ptr_ -= buffer_size_; | |||||
} | |||||
if (block_size_ >= hop_size_) { | |||||
block_size_ -= hop_size_; | |||||
++ready_; | |||||
} | |||||
} | |||||
} | |||||
void STFT::Buffer() { | |||||
if (ready_ == done_) { | |||||
return; | |||||
} | |||||
// Copy block to FFT buffer and apply window. | |||||
size_t source_ptr = process_ptr_; | |||||
const float* w = window_; | |||||
for (size_t i = 0; i < fft_size_; ++i) { | |||||
fft_in_[i] = w[0] * analysis_[source_ptr]; | |||||
++source_ptr; | |||||
if (source_ptr >= buffer_size_) { | |||||
source_ptr -= buffer_size_; | |||||
} | |||||
w += window_stride_; | |||||
} | |||||
// Compute FFT. fft_in is lost. | |||||
if (fft_size_ != FFT::max_size) { | |||||
fft_->Direct(fft_in_, fft_out_, fft_num_passes_); | |||||
} else { | |||||
fft_->Direct(fft_in_, fft_out_); | |||||
} | |||||
// Process in the frequency domain. | |||||
if (modifier_ != NULL && parameters_ != NULL) { | |||||
modifier_->Process(*parameters_, &fft_out_[0], &ifft_in_[0]); | |||||
} else { | |||||
copy(&fft_out_[0], &fft_out_[fft_size_], &ifft_in_[0]); | |||||
} | |||||
// Compute IFFT. ifft_in is lost. | |||||
if (fft_size_ != FFT::max_size) { | |||||
fft_->Inverse(ifft_in_, ifft_out_, fft_num_passes_); | |||||
} else { | |||||
fft_->Inverse(ifft_in_, ifft_out_); | |||||
} | |||||
size_t destination_ptr = process_ptr_; | |||||
float inverse_window_size = 1.0f / \ | |||||
float(fft_size_ * fft_size_ / hop_size_ >> 1); | |||||
w = window_; | |||||
for (size_t i = 0; i < fft_size_; ++i) { | |||||
float s = ifft_out_[i] * w[0] * inverse_window_size; | |||||
int32_t x = static_cast<int32_t>(s); | |||||
if (i < fft_size_ - hop_size_) { | |||||
// Overlap-add. | |||||
x += synthesis_[destination_ptr]; | |||||
} | |||||
synthesis_[destination_ptr] = Clip16(x); | |||||
++destination_ptr; | |||||
if (destination_ptr >= buffer_size_) { | |||||
destination_ptr -= buffer_size_; | |||||
} | |||||
w += window_stride_; | |||||
} | |||||
++done_; | |||||
process_ptr_ += hop_size_; | |||||
if (process_ptr_ >= buffer_size_) { | |||||
process_ptr_ -= buffer_size_; | |||||
} | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,108 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// STFT with overlap-add. | |||||
#ifndef CLOUDS_DSP_PVOC_STFT_H_ | |||||
#define CLOUDS_DSP_PVOC_STFT_H_ | |||||
#include "stmlib/stmlib.h" | |||||
// #define USE_ARM_FFT | |||||
#include "stmlib/fft/shy_fft.h" | |||||
namespace clouds { | |||||
struct Parameters; | |||||
const size_t kMaxFftSize = 4096; | |||||
typedef stmlib::ShyFFT<float, kMaxFftSize, stmlib::RotationPhasor> FFT; | |||||
typedef class FrameTransformation Modifier; | |||||
class STFT { | |||||
public: | |||||
STFT() { } | |||||
~STFT() { } | |||||
struct Frame { short l; short r; }; | |||||
void Init( | |||||
FFT* fft, | |||||
size_t fft_size, | |||||
size_t hop_size, | |||||
float* fft_buffer, | |||||
float* ifft_buffer, | |||||
const float* window_lut, | |||||
short* stft_frame_processor_buffer, | |||||
Modifier* modifier); | |||||
void Reset(); | |||||
void Process( | |||||
const Parameters& parameters, | |||||
const float* input, | |||||
float* output, | |||||
size_t size, | |||||
size_t stride); | |||||
void Buffer(); | |||||
private: | |||||
FFT* fft_; | |||||
size_t fft_size_; | |||||
size_t fft_num_passes_; | |||||
size_t hop_size_; | |||||
size_t buffer_size_; | |||||
float* fft_in_; | |||||
float* fft_out_; | |||||
float* ifft_out_; | |||||
float* ifft_in_; | |||||
const float* window_; | |||||
size_t window_stride_; | |||||
short* analysis_; | |||||
short* synthesis_; | |||||
size_t buffer_ptr_; | |||||
size_t process_ptr_; | |||||
size_t block_size_; | |||||
size_t ready_; | |||||
size_t done_; | |||||
const Parameters* parameters_; | |||||
Modifier* modifier_; | |||||
DISALLOW_COPY_AND_ASSIGN(STFT); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_PVOC_STFT_H_ |
@@ -0,0 +1,96 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Sample rate converter | |||||
#ifndef CLOUDS_DSP_SAMPLE_RATE_CONVERTER_H_ | |||||
#define CLOUDS_DSP_SAMPLE_RATE_CONVERTER_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "clouds/dsp/frame.h" | |||||
namespace clouds { | |||||
template<int32_t ratio, int32_t filter_size, const float* coefficients> | |||||
class SampleRateConverter { | |||||
public: | |||||
SampleRateConverter() { } | |||||
~SampleRateConverter() { } | |||||
void Init() { | |||||
for (int32_t i = 0; i < filter_size * 2; ++i) { | |||||
history_[i].l = history_[i].r = 0.0f; | |||||
} | |||||
std::copy(&coefficients[0], &coefficients[filter_size], &coefficients_[0]); | |||||
history_ptr_ = filter_size - 1; | |||||
}; | |||||
void Process(const FloatFrame* in, FloatFrame* out, size_t input_size) { | |||||
int32_t history_ptr = history_ptr_; | |||||
FloatFrame* history = history_; | |||||
const float scale = ratio < 0 ? 1.0f : float(ratio); | |||||
while (input_size) { | |||||
int32_t consumed = ratio < 0 ? -ratio : 1; | |||||
for (int32_t i = 0; i < consumed; ++i) { | |||||
history[history_ptr + filter_size] = history[history_ptr] = *in++; | |||||
--input_size; | |||||
--history_ptr; | |||||
if (history_ptr < 0) { | |||||
history_ptr += filter_size; | |||||
} | |||||
} | |||||
int32_t produced = ratio > 0 ? ratio : 1; | |||||
for (int32_t i = 0; i < produced; ++i) { | |||||
float y_l = 0.0f; | |||||
float y_r = 0.0f; | |||||
const FloatFrame* x = &history[history_ptr + 1]; | |||||
for (int32_t j = i; j < filter_size; j += produced) { | |||||
const float h = coefficients_[j]; | |||||
y_l += x->l * h; | |||||
y_r += x->r * h; | |||||
++x; | |||||
} | |||||
out->l = y_l * scale; | |||||
out->r = y_r * scale; | |||||
++out; | |||||
} | |||||
} | |||||
history_ptr_ = history_ptr; | |||||
} | |||||
private: | |||||
float coefficients_[filter_size]; | |||||
FloatFrame history_[filter_size * 2]; | |||||
int32_t history_ptr_; | |||||
DISALLOW_COPY_AND_ASSIGN(SampleRateConverter); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_SAMPLE_RATE_CONVERTER_H_ |
@@ -0,0 +1,124 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Variant of the grain class used for WSOLA. | |||||
#ifndef CLOUDS_DSP_WINDOW_H_ | |||||
#define CLOUDS_DSP_WINDOW_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/dsp/dsp.h" | |||||
#include "clouds/dsp/audio_buffer.h" | |||||
#include "clouds/resources.h" | |||||
namespace clouds { | |||||
enum WindowFlags { | |||||
WINDOW_FLAGS_HALF_DONE = 1, | |||||
WINDOW_FLAGS_REGENERATED = 2, | |||||
WINDOW_FLAGS_DONE = 4 | |||||
}; | |||||
class Window { | |||||
public: | |||||
Window() { } | |||||
~Window() { } | |||||
void Init() { | |||||
done_ = true; | |||||
regenerated_ = false; | |||||
half_ = false; | |||||
} | |||||
void Start( | |||||
int32_t buffer_size, | |||||
int32_t start, | |||||
int32_t width, | |||||
int32_t phase_increment) { | |||||
first_sample_ = (start + buffer_size) % buffer_size; | |||||
phase_increment_ = phase_increment; | |||||
phase_ = 0; | |||||
done_ = false; | |||||
regenerated_ = false; | |||||
done_ = false; | |||||
envelope_phase_increment_ = 2.0f / static_cast<float>(width); | |||||
} | |||||
template<Resolution resolution> | |||||
inline void OverlapAdd( | |||||
const AudioBuffer<resolution>* buffer, | |||||
float* samples, | |||||
int32_t channels) { | |||||
if (done_) { | |||||
return; | |||||
} | |||||
int32_t phase_integral = phase_ >> 16; | |||||
int32_t phase_fractional = phase_ & 0xffff; | |||||
int32_t sample_index = first_sample_ + phase_integral; | |||||
float envelope_phase = phase_integral * envelope_phase_increment_; | |||||
done_ = envelope_phase >= 2.0f; | |||||
half_ = envelope_phase >= 1.0f; | |||||
float gain = envelope_phase >= 1.0f | |||||
? 2.0f - envelope_phase | |||||
: envelope_phase; | |||||
float l = buffer[0].ReadHermite(sample_index, phase_fractional) * gain; | |||||
if (channels == 1) { | |||||
*samples++ += l; | |||||
*samples++ += l; | |||||
} else if (channels == 2) { | |||||
float r = buffer[1].ReadHermite(sample_index, phase_fractional) * gain; | |||||
*samples++ += l; | |||||
*samples++ += r; | |||||
} | |||||
phase_ += phase_increment_; | |||||
} | |||||
inline bool done() { return done_; } | |||||
inline bool needs_regeneration() { return half_ && !regenerated_; } | |||||
inline void MarkAsRegenerated() { regenerated_ = true; } | |||||
private: | |||||
Window* next_; | |||||
int32_t first_sample_; | |||||
int32_t phase_; | |||||
int32_t phase_increment_; | |||||
float envelope_phase_increment_; | |||||
bool done_; | |||||
bool half_; | |||||
bool regenerated_; | |||||
DISALLOW_COPY_AND_ASSIGN(Window); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_WINDOW_H_ |
@@ -0,0 +1,290 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// WSOLA playback. | |||||
#ifndef CLOUDS_DSP_WSOLA_SAMPLE_PLAYER_H_ | |||||
#define CLOUDS_DSP_WSOLA_SAMPLE_PLAYER_H_ | |||||
#include <algorithm> | |||||
#include <cmath> | |||||
#include <cstdio> | |||||
#include <cstdlib> | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/dsp/units.h" | |||||
#include "clouds/dsp/audio_buffer.h" | |||||
#include "clouds/dsp/correlator.h" | |||||
#include "clouds/dsp/frame.h" | |||||
#include "clouds/dsp/window.h" | |||||
#include "clouds/dsp/parameters.h" | |||||
#include "clouds/resources.h" | |||||
namespace clouds { | |||||
const int32_t kMaxWSOLASize = 4096; | |||||
using namespace stmlib; | |||||
class WSOLASamplePlayer { | |||||
public: | |||||
WSOLASamplePlayer() { } | |||||
~WSOLASamplePlayer() { } | |||||
void Init( | |||||
Correlator* correlator, | |||||
int32_t num_channels) { | |||||
correlator_ = correlator; | |||||
num_channels_ = num_channels; | |||||
pitch_ = 0.0f; | |||||
position_ = 0.0f; | |||||
smoothed_pitch_ = 0.0f; | |||||
windows_[0].Init(); | |||||
windows_[1].Init(); | |||||
next_pitch_ratio_ = 1.0f; | |||||
correlator_loaded_ = true; | |||||
search_source_ = 0; | |||||
search_target_ = 0; | |||||
window_size_ = kMaxWSOLASize / 2; | |||||
env_phase_ = 0.0f; | |||||
env_phase_increment_ = 0.5f; | |||||
elapsed_ = 0; | |||||
} | |||||
template<Resolution resolution> | |||||
void Play( | |||||
const AudioBuffer<resolution>* buffer, | |||||
const Parameters& parameters, | |||||
float* out, | |||||
size_t size) { | |||||
elapsed_++; | |||||
if (parameters.trigger) { | |||||
env_phase_ = 0.0f; | |||||
env_phase_increment_ = 1.0f / static_cast<float>(elapsed_); | |||||
CONSTRAIN(env_phase_increment_, 0.0001f, 0.1f); | |||||
elapsed_ = 0; | |||||
} | |||||
env_phase_ += env_phase_increment_; | |||||
if (env_phase_ >= 1.0f) { | |||||
env_phase_ = 1.0; | |||||
} | |||||
position_ = parameters.position; | |||||
position_ += (1.0f - env_phase_) * (1.0f - position_); | |||||
pitch_ = parameters.pitch; | |||||
size_factor_ = parameters.size; | |||||
if (windows_[0].done() && windows_[1].done()) { | |||||
windows_[1].MarkAsRegenerated(); | |||||
ScheduleAlignedWindow(buffer, &windows_[0]); | |||||
} | |||||
while (size--) { | |||||
// Sum the two windows. | |||||
std::fill(&out[0], &out[kMaxNumChannels], 0); | |||||
for (int32_t i = 0; i < 2; ++i) { | |||||
windows_[i].OverlapAdd(buffer, out, num_channels_); | |||||
} | |||||
// Regenerate expired windows. | |||||
for (int32_t i = 0; i < 2; ++i) { | |||||
if (windows_[i].needs_regeneration()) { | |||||
windows_[i].MarkAsRegenerated(); | |||||
ScheduleAlignedWindow(buffer, &windows_[1 - i]); | |||||
windows_[1 - i].OverlapAdd(buffer, out, num_channels_); | |||||
} | |||||
} | |||||
out += 2; | |||||
} | |||||
} | |||||
template<int32_t num_channels, Resolution resolution> | |||||
int32_t ReadSignBits( | |||||
const AudioBuffer<resolution>* buffer, | |||||
int32_t phase_increment, | |||||
int32_t source, | |||||
int32_t size, | |||||
uint32_t* destination) { | |||||
int32_t phase = 0; | |||||
uint32_t bits = 0; | |||||
uint32_t bit_counter = 0; | |||||
int32_t num_samples = 0; | |||||
if (source < 0) { | |||||
source += buffer->size(); | |||||
} | |||||
while ((phase >> 16) < size) { | |||||
int32_t integral = source + (phase >> 16); | |||||
uint16_t fractional = phase & 0xffff; | |||||
float s = buffer[0].ReadLinear(integral, fractional); | |||||
if (num_channels == 2) { | |||||
s += buffer[1].ReadLinear(integral, fractional); | |||||
} | |||||
bits |= s > 0.0f ? 1 : 0; | |||||
if ((bit_counter & 0x1f) == 0x1f) { | |||||
destination[bit_counter >> 5] = bits; | |||||
num_samples += 32; | |||||
} | |||||
++bit_counter; | |||||
bits <<= 1; | |||||
phase += phase_increment; | |||||
} | |||||
while (bit_counter & 0x1f) { | |||||
if ((bit_counter & 0x1f) == 0x1f) { | |||||
destination[bit_counter >> 5] = bits; | |||||
num_samples += 32; | |||||
} | |||||
++bit_counter; | |||||
bits <<= 1; | |||||
} | |||||
return num_samples; | |||||
} | |||||
template<Resolution resolution> | |||||
void LoadCorrelator(const AudioBuffer<resolution>* buffer) { | |||||
if (correlator_loaded_) { | |||||
return; | |||||
} | |||||
float stride = window_size_ / 2048.0f; | |||||
CONSTRAIN(stride, 1.0f, 2.0f); | |||||
stride *= 65536.0f; | |||||
int32_t increment = static_cast<int32_t>( | |||||
stride * (next_pitch_ratio_ < 1.25f ? 1.25f : next_pitch_ratio_)); | |||||
int32_t num_samples = 0; | |||||
if (num_channels_ == 1) { | |||||
num_samples = ReadSignBits<1>( | |||||
buffer, | |||||
increment, | |||||
search_source_, | |||||
window_size_, | |||||
correlator_->source()); | |||||
ReadSignBits<1>( | |||||
buffer, | |||||
increment, | |||||
search_target_ - window_size_, | |||||
window_size_ * 2, | |||||
correlator_->destination()); | |||||
} else { | |||||
num_samples = ReadSignBits<2>( | |||||
buffer, | |||||
increment, | |||||
search_source_, | |||||
window_size_, | |||||
correlator_->source()); | |||||
ReadSignBits<2>( | |||||
buffer, | |||||
increment, | |||||
search_target_ - window_size_, | |||||
window_size_ * 2, | |||||
correlator_->destination()); | |||||
} | |||||
correlator_->StartSearch( | |||||
num_samples, | |||||
search_target_ - window_size_ + (window_size_ >> 1), | |||||
increment); | |||||
correlator_loaded_ = true; | |||||
} | |||||
private: | |||||
template<Resolution resolution> | |||||
void ScheduleAlignedWindow( | |||||
const AudioBuffer<resolution>* buffer, | |||||
Window* window) { | |||||
int32_t next_window_position = correlator_->best_match(); | |||||
correlator_loaded_ = false; | |||||
window->Start( | |||||
buffer->size(), | |||||
next_window_position - (window_size_ >> 1), | |||||
window_size_, | |||||
static_cast<uint32_t>(next_pitch_ratio_ * 65536.0f)); | |||||
float pitch_error = pitch_ - smoothed_pitch_; | |||||
float pitch_error_sign = pitch_error < 0.0f ? -1.0 : 1.0; | |||||
pitch_error *= pitch_error_sign; | |||||
if (pitch_error >= 12.0f) { | |||||
pitch_error = 12.0f; | |||||
} | |||||
smoothed_pitch_ += pitch_error * pitch_error_sign; | |||||
float pitch_ratio = SemitonesToRatio(smoothed_pitch_); | |||||
float inv_pitch_ratio = SemitonesToRatio(-smoothed_pitch_); | |||||
next_pitch_ratio_ = pitch_ratio; | |||||
float size_factor = SemitonesToRatio((size_factor_ - 1.0f) * 60.0f); | |||||
int32_t new_window_size = static_cast<int32_t>(size_factor * kMaxWSOLASize); | |||||
if (std::abs(new_window_size - window_size_) > 64) { | |||||
int32_t error = (new_window_size - window_size_) >> 5; | |||||
new_window_size = window_size_ + error; | |||||
window_size_ = new_window_size - (new_window_size % 4); | |||||
} | |||||
// The center offset of the window we want to mix in. | |||||
int32_t limit = buffer->size(); | |||||
limit -= static_cast<int32_t>(2.0f * window_size_ * inv_pitch_ratio); | |||||
limit -= 2 * window_size_; | |||||
if (limit < 0) { | |||||
limit = 0; | |||||
} | |||||
float position = position_; | |||||
int32_t target_position = buffer->head(); | |||||
target_position -= static_cast<int32_t>(limit * position); | |||||
target_position -= window_size_; | |||||
search_source_ = next_window_position; | |||||
search_target_ = target_position; | |||||
} | |||||
Correlator* correlator_; | |||||
Window windows_[2]; | |||||
int32_t window_size_; | |||||
int32_t num_channels_; | |||||
float pitch_; | |||||
float smoothed_pitch_; | |||||
float position_; | |||||
float size_factor_; | |||||
float next_pitch_ratio_; | |||||
bool correlator_loaded_; | |||||
int32_t search_source_; | |||||
int32_t search_target_; | |||||
float env_phase_; | |||||
float env_phase_increment_; | |||||
int32_t elapsed_; | |||||
DISALLOW_COPY_AND_ASSIGN(WSOLASamplePlayer); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_DSP_WSOLA_SAMPLE_PLAYER_H_ |
@@ -0,0 +1,59 @@ | |||||
# Copyright 2014 Olivier Gillet. | |||||
# | |||||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
# THE SOFTWARE. | |||||
# | |||||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||||
# System specifications | |||||
F_CRYSTAL = 8000000L | |||||
F_CPU = 168000000L | |||||
SYSCLOCK = SYSCLK_FREQ_168MHz | |||||
FAMILY = f4xx | |||||
# USB = enabled | |||||
APPLICATION_LARGE = TRUE | |||||
BOOTLOADER = clouds_bootloader | |||||
# Preferred upload command | |||||
UPLOAD_COMMAND = upload_combo_jtag_erase_first | |||||
# Packages to build | |||||
TARGET = clouds | |||||
PACKAGES = clouds \ | |||||
clouds/drivers \ | |||||
clouds/dsp \ | |||||
clouds/dsp/pvoc \ | |||||
stmlib/dsp \ | |||||
stmlib/utils \ | |||||
stmlib/system \ | |||||
stmlib/third_party/STM/CMSIS/DSP_Lib/CommonTables \ | |||||
stmlib/third_party/STM/CMSIS/DSP_Lib/TransformFunctions | |||||
RESOURCES = clouds/resources | |||||
TOOLCHAIN_PATH ?= /usr/local/arm-4.8.3/ | |||||
include stmlib/makefile.inc | |||||
# Rule for building the firmware update file | |||||
wav: $(TARGET_BIN) | |||||
python stm_audio_bootloader/qpsk/encoder.py \ | |||||
-t stm32f4 -s 48000 -b 12000 -c 6000 -p 256 \ | |||||
$(TARGET_BIN) |
@@ -0,0 +1,84 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Vu-meter | |||||
#ifndef CLOUDS_METER_H_ | |||||
#define CLOUDS_METER_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "clouds/drivers/codec.h" | |||||
namespace clouds { | |||||
class Meter { | |||||
public: | |||||
Meter() { } | |||||
~Meter() { } | |||||
void Init(int32_t sample_rate) { | |||||
attack_ = 32768 * 100 / sample_rate; | |||||
release_ = 32768 * 4 / sample_rate; | |||||
peak_l_ = 0; | |||||
peak_r_ = 0; | |||||
} | |||||
void Process(Codec::Frame* frames, size_t size) { | |||||
while (size--) { | |||||
int32_t sample; | |||||
int32_t error; | |||||
int32_t coefficient; | |||||
sample = frames->l; | |||||
if (sample < 0) sample = -sample; | |||||
error = sample - peak_l_; | |||||
coefficient = error > 0 ? attack_ : release_; | |||||
peak_l_ += error * coefficient >> 15; | |||||
sample = frames->r; | |||||
if (sample < 0) sample = -sample; | |||||
error = sample - peak_r_; | |||||
coefficient = error > 0 ? attack_ : release_; | |||||
peak_r_ += error * coefficient >> 15; | |||||
++frames; | |||||
} | |||||
} | |||||
int32_t peak() { return peak_l_ > peak_r_ ? peak_l_ : peak_r_; } | |||||
private: | |||||
int32_t peak_l_; | |||||
int32_t peak_r_; | |||||
int32_t attack_; | |||||
int32_t release_; | |||||
DISALLOW_COPY_AND_ASSIGN(Meter); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_METER_H_ |
@@ -0,0 +1,93 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Resources definitions. | |||||
// | |||||
// Automatically generated with: | |||||
// make resources | |||||
#ifndef CLOUDS_RESOURCES_H_ | |||||
#define CLOUDS_RESOURCES_H_ | |||||
#include "stmlib/stmlib.h" | |||||
namespace clouds { | |||||
typedef uint8_t ResourceId; | |||||
extern const float* src_filter_table[]; | |||||
extern const int16_t* lookup_table_int16_table[]; | |||||
extern const float* lookup_table_table[]; | |||||
extern const float src_filter_1x_2_31[]; | |||||
extern const float src_filter_1x_2_45[]; | |||||
extern const float src_filter_1x_2_63[]; | |||||
extern const float src_filter_1x_2_91[]; | |||||
extern const int16_t lut_db[]; | |||||
extern const float lut_sin[]; | |||||
extern const float lut_window[]; | |||||
extern const float lut_xfade_in[]; | |||||
extern const float lut_xfade_out[]; | |||||
extern const float lut_sine_window_4096[]; | |||||
extern const float lut_cutoff[]; | |||||
extern const float lut_grain_size[]; | |||||
extern const float lut_quantized_pitch[]; | |||||
#define SRC_FILTER_1X_2_31 0 | |||||
#define SRC_FILTER_1X_2_31_SIZE 31 | |||||
#define SRC_FILTER_1X_2_45 1 | |||||
#define SRC_FILTER_1X_2_45_SIZE 45 | |||||
#define SRC_FILTER_1X_2_63 2 | |||||
#define SRC_FILTER_1X_2_63_SIZE 63 | |||||
#define SRC_FILTER_1X_2_91 3 | |||||
#define SRC_FILTER_1X_2_91_SIZE 91 | |||||
#define LUT_DB 0 | |||||
#define LUT_DB_SIZE 257 | |||||
#define LUT_SIN 0 | |||||
#define LUT_SIN_SIZE 1281 | |||||
#define LUT_WINDOW 1 | |||||
#define LUT_WINDOW_SIZE 4097 | |||||
#define LUT_XFADE_IN 2 | |||||
#define LUT_XFADE_IN_SIZE 17 | |||||
#define LUT_XFADE_OUT 3 | |||||
#define LUT_XFADE_OUT_SIZE 17 | |||||
#define LUT_SINE_WINDOW_4096 4 | |||||
#define LUT_SINE_WINDOW_4096_SIZE 4096 | |||||
#define LUT_CUTOFF 5 | |||||
#define LUT_CUTOFF_SIZE 257 | |||||
#define LUT_GRAIN_SIZE 6 | |||||
#define LUT_GRAIN_SIZE_SIZE 257 | |||||
#define LUT_QUANTIZED_PITCH 7 | |||||
#define LUT_QUANTIZED_PITCH_SIZE 1025 | |||||
} // namespace clouds | |||||
#endif // CLOUDS_RESOURCES_H_ |
@@ -0,0 +1,156 @@ | |||||
#!/usr/bin/python2.5 | |||||
# | |||||
# Copyright 2014 Olivier Gillet. | |||||
# | |||||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
# THE SOFTWARE. | |||||
# | |||||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||||
# | |||||
# ----------------------------------------------------------------------------- | |||||
# | |||||
# Lookup table definitions. | |||||
import scipy.signal | |||||
import numpy | |||||
import pylab | |||||
lookup_tables = [] | |||||
int16_lookup_tables = [] | |||||
"""---------------------------------------------------------------------------- | |||||
Cosine table. | |||||
----------------------------------------------------------------------------""" | |||||
size = 1024 | |||||
t = numpy.arange(0, size + size / 4 + 1) / float(size) * numpy.pi * 2 | |||||
lookup_tables.append(('sin', numpy.sin(t))) | |||||
"""---------------------------------------------------------------------------- | |||||
Grain window. | |||||
----------------------------------------------------------------------------""" | |||||
size = 4096 | |||||
t = numpy.arange(0, size + 1) / float(size) | |||||
lookup_tables.append(('window', 1.0 - (numpy.cos(t * numpy.pi) + 1) / 2)) | |||||
"""---------------------------------------------------------------------------- | |||||
XFade table | |||||
----------------------------------------------------------------------------""" | |||||
size = 17 | |||||
t = numpy.arange(0, size) / float(size-1) | |||||
t = 1.04 * t - 0.02 | |||||
t[t < 0] = 0 | |||||
t[t >= 1] = 1 | |||||
t *= numpy.pi / 2 | |||||
lookup_tables.append(('xfade_in', numpy.sin(t) * 2 ** -0.5)) | |||||
lookup_tables.append(('xfade_out', numpy.cos(t) * 2 ** -0.5)) | |||||
"""---------------------------------------------------------------------------- | |||||
Sine window. | |||||
----------------------------------------------------------------------------""" | |||||
def sum_window(window, steps): | |||||
n = window.shape[0] | |||||
start = 0 | |||||
stride = n / steps | |||||
s = 0 | |||||
for i in xrange(steps): | |||||
s = s + window[start:start+stride] ** 2 | |||||
start += stride | |||||
return s | |||||
window_size = 4096 | |||||
t = numpy.arange(0.0, window_size) / window_size | |||||
# Perfect reconstruction for overlap of 2 | |||||
sine = numpy.sin(numpy.pi * t) | |||||
# Perfect reconstruction for overlap of 4 | |||||
raised = (0.5 * numpy.cos(numpy.pi * t * 2) + 0.5) * numpy.sqrt(4.0 / 3.0) | |||||
# Needs tweaks to provide good reconstruction | |||||
power = (1.0 - (2 * t - 1.0) ** 2.0) ** 1.25 | |||||
compensation = sum_window(power, 2) ** 0.5 | |||||
compensation = numpy.array(list(compensation) * 2) | |||||
power /= compensation | |||||
lookup_tables.append(('sine_window_4096', power)) | |||||
"""---------------------------------------------------------------------------- | |||||
Linear to dB, for display | |||||
----------------------------------------------------------------------------""" | |||||
db = numpy.arange(0, 257) | |||||
db[0] = 1 | |||||
db[db > 255] = 255 | |||||
db = numpy.log2(db / 16.0) * 32768 / 4 | |||||
int16_lookup_tables += [('db', db)] | |||||
"""---------------------------------------------------------------------------- | |||||
LPG cutoff | |||||
----------------------------------------------------------------------------""" | |||||
TABLE_SIZE = 256 | |||||
cutoff = numpy.arange(0.0, TABLE_SIZE + 1) / TABLE_SIZE | |||||
lookup_tables.append(('cutoff', 0.49 * 2 ** (-6 * (1 - cutoff)))) | |||||
"""---------------------------------------------------------------------------- | |||||
Grain size table | |||||
----------------------------------------------------------------------------""" | |||||
size = numpy.arange(0.0, TABLE_SIZE + 1) / TABLE_SIZE * 4 | |||||
lookup_tables.append(('grain_size', numpy.floor(1024 * (2 ** size)))) | |||||
"""---------------------------------------------------------------------------- | |||||
Quantizer for pitch. | |||||
----------------------------------------------------------------------------""" | |||||
PITCH_TABLE_SIZE = 1025 | |||||
pitch = numpy.zeros((PITCH_TABLE_SIZE, )) | |||||
notches = [-24, -12, -7, -4, -3, -1, -0.1, 0, 0, 0.1, 1, 3, 4, 7, 12, 24] | |||||
n = len(notches) - 1 | |||||
for i in xrange(n): | |||||
start_index = int(float(i) / n * PITCH_TABLE_SIZE) | |||||
end_index = int(float(i + 1) / n * PITCH_TABLE_SIZE) | |||||
length = end_index - start_index | |||||
x = numpy.arange(0.0, length) / (length - 1) | |||||
raised_cosine = 0.5 - 0.5 * numpy.cos(x * numpy.pi) | |||||
xfade = 0.8 * raised_cosine + 0.2 * x | |||||
pitch[start_index:end_index] = notches[i] + (notches[i + 1] - notches[i]) * xfade | |||||
lookup_tables.append(('quantized_pitch', pitch)) |
@@ -0,0 +1,82 @@ | |||||
#!/usr/bin/python2.5 | |||||
# | |||||
# Copyright 2014 Olivier Gillet. | |||||
# | |||||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
# THE SOFTWARE. | |||||
# | |||||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||||
# | |||||
# ----------------------------------------------------------------------------- | |||||
# | |||||
# Master resources file. | |||||
header = """// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Resources definitions. | |||||
// | |||||
// Automatically generated with: | |||||
// make resources | |||||
""" | |||||
namespace = 'clouds' | |||||
target = 'clouds' | |||||
types = ['uint8_t', 'uint16_t'] | |||||
includes = """ | |||||
#include "stmlib/stmlib.h" | |||||
""" | |||||
import lookup_tables | |||||
import src_filters | |||||
create_specialized_manager = True | |||||
resources = [ | |||||
(src_filters.filters, | |||||
'src_filter', 'SRC', 'float', float, False), | |||||
(lookup_tables.int16_lookup_tables, | |||||
'lookup_table_int16', 'LUT', 'int16_t', int, False), | |||||
(lookup_tables.lookup_tables, | |||||
'lookup_table', 'LUT', 'float', float, False), | |||||
] |
@@ -0,0 +1,83 @@ | |||||
#!/usr/bin/python2.5 | |||||
# | |||||
# Copyright 2014 Olivier Gillet. | |||||
# | |||||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
# THE SOFTWARE. | |||||
# | |||||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||||
# | |||||
# ----------------------------------------------------------------------------- | |||||
# | |||||
# Lookup table definitions. | |||||
import numpy | |||||
import pylab | |||||
import scipy.signal | |||||
SAMPLE_RATE = 32000 | |||||
CRITICAL_FREQUENCY = 12000.0 | |||||
IR_LENGTH = 8192 | |||||
filters = [] | |||||
ratios = [2] | |||||
lengths = [31, 45, 63, 91] | |||||
configurations = [] | |||||
for oversampled in [False]: | |||||
for ratio in ratios: | |||||
for length in lengths: | |||||
configurations += [(oversampled, ratio, length)] | |||||
generate_figures = False | |||||
for oversampled, ratio, length in configurations: | |||||
transition = 0.15 * 0.25 ** (length / 100.0) if oversampled else 0.4 | |||||
ir = scipy.signal.remez( | |||||
length, | |||||
[0, transition / ratio, 0.499 / ratio, 0.5], [1, 0]) | |||||
ir /= sum(ir) | |||||
gain = 20 * numpy.log10(numpy.abs(numpy.fft.rfft(ir, IR_LENGTH))) | |||||
f = numpy.arange(IR_LENGTH / 2 + 1) / float(IR_LENGTH) * SAMPLE_RATE * ratio | |||||
bin_index = CRITICAL_FREQUENCY / ratio / SAMPLE_RATE * IR_LENGTH | |||||
critical_gain = gain[int(round(bin_index))] | |||||
name = 'filter_%s_%d_%d' % ('2x' if oversampled else '1x', ratio, length) | |||||
if generate_figures: | |||||
pylab.figure(figsize=(8, 12)) | |||||
pylab.subplot(211) | |||||
pylab.plot(f, gain) | |||||
pylab.xlim([0.0, SAMPLE_RATE]) | |||||
pylab.ylim([-75.0, 3.0]) | |||||
pylab.xlabel('Frequency (Hz)') | |||||
pylab.ylabel('Gain (dB)') | |||||
caption = 'Resampling filter, N=%d, Ratio=%d. Gain at %d Hz = %.2fdB' | |||||
pylab.title(caption % (length, ratio, CRITICAL_FREQUENCY, critical_gain)) | |||||
pylab.subplot(212) | |||||
pylab.plot(f, gain) | |||||
pylab.xlim([0.0, 20000.0]) | |||||
pylab.ylim([-3.0, 1.0]) | |||||
pylab.xlabel('Frequency (Hz)') | |||||
pylab.ylabel('Gain (dB)') | |||||
pylab.savefig(name + '.pdf') | |||||
pylab.close() | |||||
filters += [(name, ir)] |
@@ -0,0 +1,89 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Settings storage. | |||||
#include "clouds/settings.h" | |||||
#include "stmlib/system/storage.h" | |||||
#include "clouds/dsp/granular_processor.h" | |||||
namespace clouds { | |||||
stmlib::Storage<1> storage; | |||||
void Settings::Init() { | |||||
freshly_baked_ = false; | |||||
if (!storage.ParsimoniousLoad(&data_, &version_token_)) { | |||||
data_.calibration_data.pitch_offset = 66.67f; | |||||
data_.calibration_data.pitch_scale = -84.26f; | |||||
for (size_t i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||||
data_.calibration_data.offset[i] = 0.505f; | |||||
} | |||||
data_.state.quality = 0; | |||||
data_.state.blend_parameter = 0; | |||||
data_.state.playback_mode = PLAYBACK_MODE_GRANULAR; | |||||
data_.state.blend_value[0] = 255; | |||||
data_.state.blend_value[1] = 128; | |||||
data_.state.blend_value[2] = 0; | |||||
data_.state.blend_value[3] = 0; | |||||
freshly_baked_ = true; | |||||
Save(); | |||||
} | |||||
} | |||||
void Settings::SaveSampleMemory( | |||||
uint32_t index, | |||||
PersistentBlock* blocks, | |||||
size_t num_blocks) { | |||||
uint32_t* data = mutable_sample_flash_data(index); | |||||
// Unprotect flash and erase sector. | |||||
FLASH_Unlock(); | |||||
FLASH_ClearFlag( | |||||
FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | | |||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR| FLASH_FLAG_PGSERR); | |||||
FLASH_EraseSector(sample_flash_sector(index) * 8, VoltageRange_3); | |||||
// Write all data blocks. | |||||
for (size_t block = 0; block < num_blocks; ++block) { | |||||
FLASH_ProgramWord((uint32_t)(data++), blocks[block].tag); | |||||
FLASH_ProgramWord((uint32_t)(data++), blocks[block].size); | |||||
size_t size = blocks[block].size; | |||||
const uint32_t* words = (const uint32_t*)(blocks[block].data); | |||||
while (size >= 4) { | |||||
FLASH_ProgramWord((uint32_t)(data++), *words++); | |||||
size -= 4; | |||||
} | |||||
} | |||||
} | |||||
void Settings::Save() { | |||||
storage.ParsimoniousSave(data_, &version_token_); | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,113 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Settings storage. | |||||
#ifndef CLOUDS_SETTINGS_H_ | |||||
#define CLOUDS_SETTINGS_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "clouds/drivers/adc.h" | |||||
namespace clouds { | |||||
struct CalibrationData { | |||||
float pitch_offset; | |||||
float pitch_scale; | |||||
float offset[ADC_CHANNEL_LAST]; | |||||
}; | |||||
struct State { | |||||
uint8_t quality; | |||||
uint8_t blend_parameter; | |||||
uint8_t playback_mode; | |||||
uint8_t blend_value[4]; | |||||
uint8_t padding; | |||||
}; | |||||
struct SettingsData { | |||||
CalibrationData calibration_data; // 48 bytes | |||||
State state; // 8 bytes | |||||
uint8_t padding[8]; | |||||
}; | |||||
class PersistentBlock; | |||||
class Settings { | |||||
public: | |||||
Settings() { } | |||||
~Settings() { } | |||||
void Init(); | |||||
void Save(); | |||||
inline const uint32_t* sample_flash_data(uint32_t index) const { | |||||
return (const uint32_t*)(mutable_sample_flash_data(index)); | |||||
} | |||||
inline uint32_t* mutable_sample_flash_data(uint32_t index) const { | |||||
return (uint32_t*)(0x08080000 + index * 0x0020000); | |||||
} | |||||
inline uint32_t sample_flash_sector(uint32_t index) { | |||||
return index + 8; | |||||
} | |||||
void SaveSampleMemory( | |||||
uint32_t index, | |||||
PersistentBlock* blocks, | |||||
size_t num_blocks); | |||||
inline CalibrationData* mutable_calibration_data() { | |||||
return &data_.calibration_data; | |||||
} | |||||
inline State* mutable_state() { | |||||
return &data_.state; | |||||
} | |||||
inline const State& state() const { | |||||
return data_.state; | |||||
} | |||||
// True when no calibration data has been found on flash sector 1, that is | |||||
// to say when the module has just been flashed. | |||||
inline bool freshly_baked() const { | |||||
return freshly_baked_; | |||||
} | |||||
private: | |||||
bool freshly_baked_; | |||||
SettingsData data_; | |||||
uint16_t version_token_; | |||||
DISALLOW_COPY_AND_ASSIGN(Settings); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_SETTINGS_H_ |
@@ -0,0 +1,230 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
#include <cassert> | |||||
#include <cmath> | |||||
#include <cstdlib> | |||||
#include <cstring> | |||||
#include <vector> | |||||
#include <xmmintrin.h> | |||||
#include "clouds/dsp/granular_processor.h" | |||||
#include "clouds/resources.h" | |||||
using namespace clouds; | |||||
using namespace std; | |||||
using namespace stmlib; | |||||
const size_t kSampleRate = 32000; | |||||
const size_t kBlockSize = 32; | |||||
void write_wav_header(FILE* fp, int num_samples, int num_channels) { | |||||
uint32_t l; | |||||
uint16_t s; | |||||
fwrite("RIFF", 4, 1, fp); | |||||
l = 36 + num_samples * 2 * num_channels; | |||||
fwrite(&l, 4, 1, fp); | |||||
fwrite("WAVE", 4, 1, fp); | |||||
fwrite("fmt ", 4, 1, fp); | |||||
l = 16; | |||||
fwrite(&l, 4, 1, fp); | |||||
s = 1; | |||||
fwrite(&s, 2, 1, fp); | |||||
s = num_channels; | |||||
fwrite(&s, 2, 1, fp); | |||||
l = kSampleRate; | |||||
fwrite(&l, 4, 1, fp); | |||||
l = static_cast<uint32_t>(kSampleRate) * 2 * num_channels; | |||||
fwrite(&l, 4, 1, fp); | |||||
s = 2 * num_channels; | |||||
fwrite(&s, 2, 1, fp); | |||||
s = 16; | |||||
fwrite(&s, 2, 1, fp); | |||||
fwrite("data", 4, 1, fp); | |||||
l = num_samples * 2 * num_channels; | |||||
fwrite(&l, 4, 1, fp); | |||||
} | |||||
void TestDSP() { | |||||
size_t duration = 19; | |||||
FILE* fp_out = fopen("clouds.wav", "wb"); | |||||
FILE* fp_in = fopen("audio_samples/kettel_32k.wav", "rb"); | |||||
size_t remaining_samples = kSampleRate * duration; | |||||
write_wav_header(fp_out, remaining_samples, 2); | |||||
fseek(fp_in, 48, SEEK_SET); | |||||
uint8_t large_buffer[118784]; | |||||
uint8_t small_buffer[65536 - 128]; | |||||
GranularProcessor processor; | |||||
processor.Init( | |||||
&large_buffer[0], sizeof(large_buffer), | |||||
&small_buffer[0],sizeof(small_buffer)); | |||||
processor.set_num_channels(2); | |||||
processor.set_low_fidelity(false); | |||||
processor.set_playback_mode(PLAYBACK_MODE_LOOPING_DELAY); | |||||
Parameters* p = processor.mutable_parameters(); | |||||
size_t block_counter = 0; | |||||
float phase_ = 0.0f; | |||||
bool synthetic = true; | |||||
processor.Prepare(); | |||||
float pot_noise = 0.0f; | |||||
while (remaining_samples) { | |||||
uint16_t tri = (remaining_samples * 2); | |||||
tri = tri > 32767 ? 65535 - tri : tri; | |||||
float triangle = tri / 32768.0f; | |||||
p->gate = false; | |||||
p->trigger = false; | |||||
p->freeze = true && (block_counter & 2047) > 1024; | |||||
pot_noise += 0.05f * ((Random::GetSample() / 32768.0f) * 0.00f - pot_noise); | |||||
p->position = triangle * 0.0f + 0.00f; | |||||
p->size = 0.5f; | |||||
p->pitch = -7.0f + (triangle > 0.5f ? 1.0f : 0.0f) * 0.0f; | |||||
p->density = 0.0f; | |||||
p->texture = 0.5f; | |||||
p->feedback = 0.0f; | |||||
p->dry_wet = 1.0f; | |||||
p->reverb = 0.0f; | |||||
p->stereo_spread = 0.0f; | |||||
++block_counter; | |||||
ShortFrame input[kBlockSize]; | |||||
ShortFrame output[kBlockSize]; | |||||
if (synthetic) { | |||||
for (size_t i = 0; i < kBlockSize; ++i) { | |||||
phase_ += 400.0f / kSampleRate; // (block_counter & 512 ? 110.0f : 220.0f) / kSampleRate; | |||||
while (phase_ >= 1.0) { | |||||
phase_ -= 1.0; | |||||
} | |||||
input[i].l = 16384.0f * sinf(phase_ * M_PI * 2); | |||||
// input[i].r = 32768.0f * (phase_ - 0.5); | |||||
input[i].r = input[i].l; | |||||
} | |||||
remaining_samples -= kBlockSize; | |||||
} else { | |||||
if (fread( | |||||
input, | |||||
sizeof(ShortFrame), | |||||
kBlockSize, | |||||
fp_in) != kBlockSize) { | |||||
break; | |||||
} | |||||
remaining_samples -= kBlockSize; | |||||
} | |||||
processor.Process(input, output, kBlockSize); | |||||
processor.Prepare(); | |||||
fwrite(output, sizeof(ShortFrame), kBlockSize, fp_out); | |||||
} | |||||
fclose(fp_out); | |||||
fclose(fp_in); | |||||
} | |||||
void TestGrainSize() { | |||||
for (int32_t _p = 0; _p < 3; _p++) { | |||||
for (int32_t _s = 0; _s < 3; _s++) { | |||||
for (int32_t _pi = 0; _pi < 3; _pi++) { | |||||
size_t duration = 19; | |||||
char name[80]; | |||||
sprintf(name, "clouds_%d%d%d.wav", _p, _s, _pi); | |||||
FILE* fp_out = fopen(name, "wb"); | |||||
FILE* fp_in = fopen("audio_samples/kettel_32k.wav", "rb"); | |||||
size_t remaining_samples = kSampleRate * duration; | |||||
write_wav_header(fp_out, remaining_samples, 2); | |||||
fseek(fp_in, 48, SEEK_SET); | |||||
uint8_t large_buffer[118784]; | |||||
uint8_t small_buffer[65536]; | |||||
GranularProcessor processor; | |||||
processor.Init( | |||||
&large_buffer[0], sizeof(large_buffer), | |||||
&small_buffer[0],sizeof(small_buffer)); | |||||
processor.set_num_channels(2); | |||||
processor.set_low_fidelity(false); | |||||
processor.set_playback_mode(PLAYBACK_MODE_GRANULAR); | |||||
Parameters* p = processor.mutable_parameters(); | |||||
size_t block_counter = 0; | |||||
processor.Prepare(); | |||||
while (remaining_samples) { | |||||
p->trigger = false; | |||||
p->freeze = (block_counter & 1023) > 512; | |||||
p->position = _p * 0.5f; | |||||
p->size = _s * 0.5f; | |||||
p->pitch = _pi * 24.0f - 24.0f; | |||||
p->density = 0.75f; | |||||
p->texture = 0.5f; | |||||
p->feedback = 0.0f; | |||||
p->dry_wet = 1.0f; | |||||
p->reverb = 0.0f; | |||||
p->stereo_spread = 0.0f; | |||||
++block_counter; | |||||
ShortFrame input[kBlockSize]; | |||||
ShortFrame output[kBlockSize]; | |||||
uint16_t tri = (remaining_samples / 4); | |||||
tri = tri > 32767 ? 65535 - tri : tri; | |||||
if (fread( | |||||
input, | |||||
sizeof(ShortFrame), | |||||
kBlockSize, | |||||
fp_in) != kBlockSize) { | |||||
break; | |||||
} | |||||
remaining_samples -= kBlockSize; | |||||
processor.Process(input, output, kBlockSize); | |||||
processor.Prepare(); | |||||
fwrite(output, sizeof(ShortFrame), kBlockSize, fp_out); | |||||
} | |||||
fclose(fp_out); | |||||
fclose(fp_in); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
int main(void) { | |||||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); | |||||
TestDSP(); | |||||
// TestGrainSize(); | |||||
} |
@@ -0,0 +1,44 @@ | |||||
PACKAGES = clouds/dsp clouds/dsp/pvoc clouds/test stmlib/utils stmlib/dsp clouds | |||||
VPATH = $(PACKAGES) | |||||
TARGET = clouds_test | |||||
BUILD_ROOT = build/ | |||||
BUILD_DIR = $(BUILD_ROOT)$(TARGET)/ | |||||
CC_FILES = atan.cc \ | |||||
clouds_test.cc \ | |||||
correlator.cc \ | |||||
granular_processor.cc \ | |||||
mu_law.cc \ | |||||
random.cc \ | |||||
resources.cc \ | |||||
frame_transformation.cc \ | |||||
phase_vocoder.cc \ | |||||
stft.cc \ | |||||
units.cc | |||||
OBJ_FILES = $(CC_FILES:.cc=.o) | |||||
OBJS = $(patsubst %,$(BUILD_DIR)%,$(OBJ_FILES)) $(STARTUP_OBJ) | |||||
DEPS = $(OBJS:.o=.d) | |||||
DEP_FILE = $(BUILD_DIR)depends.mk | |||||
all: clouds_test | |||||
$(BUILD_DIR): | |||||
mkdir -p $(BUILD_DIR) | |||||
$(BUILD_DIR)%.o: %.cc | |||||
g++ -c -DTEST -g -Wall -Werror -I. $< -o $@ | |||||
$(BUILD_DIR)%.d: %.cc | |||||
g++ -MM -DTEST -I. $< -MF $@ -MT $(@:.d=.o) | |||||
clouds_test: $(OBJS) | |||||
g++ -o $(TARGET) $(OBJS) | |||||
depends: $(DEPS) | |||||
cat $(DEPS) > $(DEP_FILE) | |||||
$(DEP_FILE): $(BUILD_DIR) $(DEPS) | |||||
cat $(DEPS) > $(DEP_FILE) | |||||
include $(DEP_FILE) |
@@ -0,0 +1,392 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// User interface. | |||||
#include "clouds/ui.h" | |||||
#include "stmlib/system/system_clock.h" | |||||
#include "clouds/dsp/granular_processor.h" | |||||
#include "clouds/cv_scaler.h" | |||||
#include "clouds/meter.h" | |||||
namespace clouds { | |||||
const int32_t kLongPressDuration = 1000; | |||||
const int32_t kVeryLongPressDuration = 4000; | |||||
using namespace stmlib; | |||||
void Ui::Init( | |||||
Settings* settings, | |||||
CvScaler* cv_scaler, | |||||
GranularProcessor* processor, | |||||
Meter* meter) { | |||||
settings_ = settings; | |||||
cv_scaler_ = cv_scaler; | |||||
leds_.Init(); | |||||
switches_.Init(); | |||||
processor_ = processor; | |||||
meter_ = meter; | |||||
mode_ = UI_MODE_SPLASH; | |||||
const State& state = settings_->state(); | |||||
// Sanitize saved settings. | |||||
cv_scaler_->set_blend_parameter( | |||||
static_cast<BlendParameter>(state.blend_parameter & 3)); | |||||
cv_scaler_->MatchKnobPosition(); | |||||
processor_->set_quality(state.quality & 3); | |||||
processor_->set_playback_mode( | |||||
static_cast<PlaybackMode>(state.playback_mode & 3)); | |||||
for (int32_t i = 0; i < BLEND_PARAMETER_LAST; ++i) { | |||||
cv_scaler_->set_blend_value( | |||||
static_cast<BlendParameter>(i), | |||||
static_cast<float>(state.blend_value[i]) / 255.0f); | |||||
} | |||||
cv_scaler_->UnlockBlendKnob(); | |||||
} | |||||
void Ui::SaveState() { | |||||
State* state = settings_->mutable_state(); | |||||
state->blend_parameter = cv_scaler_->blend_parameter(); | |||||
state->quality = processor_->quality(); | |||||
state->playback_mode = processor_->playback_mode(); | |||||
for (int32_t i = 0; i < BLEND_PARAMETER_LAST; ++i) { | |||||
state->blend_value[i] = static_cast<uint8_t>( | |||||
cv_scaler_->blend_value(static_cast<BlendParameter>(i)) * 255.0f); | |||||
} | |||||
settings_->Save(); | |||||
} | |||||
void Ui::Poll() { | |||||
system_clock.Tick(); | |||||
switches_.Debounce(); | |||||
for (uint8_t i = 0; i < kNumSwitches; ++i) { | |||||
if (switches_.just_pressed(i)) { | |||||
queue_.AddEvent(CONTROL_SWITCH, i, 0); | |||||
press_time_[i] = system_clock.milliseconds(); | |||||
long_press_time_[i] = system_clock.milliseconds(); | |||||
} | |||||
if (switches_.pressed(i) && press_time_[i] != 0) { | |||||
int32_t pressed_time = system_clock.milliseconds() - press_time_[i]; | |||||
if (pressed_time > kLongPressDuration) { | |||||
queue_.AddEvent(CONTROL_SWITCH, i, pressed_time); | |||||
press_time_[i] = 0; | |||||
} | |||||
} | |||||
if (switches_.pressed(i) && long_press_time_[i] != 0) { | |||||
int32_t pressed_time = system_clock.milliseconds() - long_press_time_[i]; | |||||
if (pressed_time > kVeryLongPressDuration) { | |||||
queue_.AddEvent(CONTROL_SWITCH, i, pressed_time); | |||||
long_press_time_[i] = 0; | |||||
} | |||||
} | |||||
if (switches_.released(i) && press_time_[i] != 0) { | |||||
queue_.AddEvent( | |||||
CONTROL_SWITCH, | |||||
i, | |||||
system_clock.milliseconds() - press_time_[i] + 1); | |||||
press_time_[i] = 0; | |||||
} | |||||
} | |||||
PaintLeds(); | |||||
} | |||||
void Ui::PaintLeds() { | |||||
leds_.Clear(); | |||||
bool blink = (system_clock.milliseconds() & 127) > 64; | |||||
uint8_t fade = system_clock.milliseconds() >> 1; | |||||
fade = fade <= 127 ? (fade << 1) : 255 - (fade << 1); | |||||
fade = static_cast<uint16_t>(fade) * fade >> 8; | |||||
switch (mode_) { | |||||
case UI_MODE_SPLASH: | |||||
{ | |||||
uint8_t index = ((system_clock.milliseconds() >> 8) + 1) & 3; | |||||
uint8_t fade = (system_clock.milliseconds() >> 2); | |||||
fade = fade <= 127 ? (fade << 1) : 255 - (fade << 1); | |||||
leds_.set_intensity(3 - index, fade); | |||||
} | |||||
break; | |||||
case UI_MODE_VU_METER: | |||||
leds_.PaintBar(lut_db[meter_->peak() >> 7]); | |||||
break; | |||||
case UI_MODE_BLEND_METER: | |||||
for (int32_t i = 0; i < 4; ++i) { | |||||
leds_.set_intensity( | |||||
i, | |||||
cv_scaler_->blend_value(static_cast<BlendParameter>(i)) * 255.0f); | |||||
} | |||||
break; | |||||
case UI_MODE_QUALITY: | |||||
leds_.set_status(processor_->quality(), 255, 0); | |||||
break; | |||||
case UI_MODE_BLENDING: | |||||
leds_.set_status(cv_scaler_->blend_parameter(), 0, 255); | |||||
break; | |||||
case UI_MODE_PLAYBACK_MODE: | |||||
leds_.set_status( | |||||
processor_->playback_mode(), | |||||
128 + (fade >> 1), | |||||
255 - (fade >> 1)); | |||||
break; | |||||
case UI_MODE_LOAD: | |||||
leds_.set_status(load_save_location_, 0, blink ? 255 : 0); | |||||
break; | |||||
case UI_MODE_SAVE: | |||||
leds_.set_status(load_save_location_, blink ? 255 : 0, 0); | |||||
break; | |||||
case UI_MODE_SAVING: | |||||
leds_.set_status(load_save_location_, 255, 0); | |||||
break; | |||||
case UI_MODE_CALIBRATION_1: | |||||
leds_.set_status(0, blink ? 255 : 0, blink ? 255 : 0); | |||||
leds_.set_status(1, blink ? 255 : 0, blink ? 255 : 0); | |||||
leds_.set_status(2, 0, 0); | |||||
leds_.set_status(3, 0, 0); | |||||
break; | |||||
case UI_MODE_CALIBRATION_2: | |||||
leds_.set_status(0, blink ? 255 : 0, blink ? 255 : 0); | |||||
leds_.set_status(1, blink ? 255 : 0, blink ? 255 : 0); | |||||
leds_.set_status(2, blink ? 255 : 0, blink ? 255 : 0); | |||||
leds_.set_status(3, blink ? 255 : 0, blink ? 255 : 0); | |||||
break; | |||||
case UI_MODE_PANIC: | |||||
leds_.set_status(0, 255, 0); | |||||
leds_.set_status(1, 255, 0); | |||||
leds_.set_status(2, 255, 0); | |||||
leds_.set_status(3, 255, 0); | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
leds_.set_freeze(processor_->frozen()); | |||||
if (processor_->bypass()) { | |||||
leds_.PaintBar(lut_db[meter_->peak() >> 7]); | |||||
leds_.set_freeze(true); | |||||
} | |||||
leds_.Write(); | |||||
} | |||||
void Ui::FlushEvents() { | |||||
queue_.Flush(); | |||||
} | |||||
void Ui::OnSwitchPressed(const Event& e) { | |||||
if (e.control_id == SWITCH_FREEZE) { | |||||
processor_->ToggleFreeze(); | |||||
} | |||||
} | |||||
void Ui::CalibrateC1() { | |||||
cv_scaler_->CalibrateC1(); | |||||
cv_scaler_->CalibrateOffsets(); | |||||
mode_ = UI_MODE_CALIBRATION_2; | |||||
} | |||||
void Ui::CalibrateC3() { | |||||
bool success = cv_scaler_->CalibrateC3(); | |||||
if (success) { | |||||
settings_->Save(); | |||||
mode_ = UI_MODE_VU_METER; | |||||
} else { | |||||
mode_ = UI_MODE_PANIC; | |||||
} | |||||
} | |||||
void Ui::OnSecretHandshake() { | |||||
mode_ = UI_MODE_PLAYBACK_MODE; | |||||
} | |||||
void Ui::OnSwitchReleased(const Event& e) { | |||||
switch (e.control_id) { | |||||
case SWITCH_FREEZE: | |||||
break; | |||||
case SWITCH_MODE: | |||||
if (e.data >= kVeryLongPressDuration) { | |||||
mode_ = UI_MODE_PLAYBACK_MODE; | |||||
} else if (e.data >= kLongPressDuration) { | |||||
if (mode_ == UI_MODE_QUALITY) { | |||||
mode_ = UI_MODE_VU_METER; | |||||
} else { | |||||
mode_ = UI_MODE_QUALITY; | |||||
} | |||||
} else if (mode_ == UI_MODE_VU_METER || mode_ == UI_MODE_BLEND_METER) { | |||||
mode_ = UI_MODE_BLENDING; | |||||
} else if (mode_ == UI_MODE_BLENDING) { | |||||
uint8_t parameter = (cv_scaler_->blend_parameter() + 1) & 3; | |||||
cv_scaler_->set_blend_parameter(static_cast<BlendParameter>(parameter)); | |||||
SaveState(); | |||||
} else if (mode_ == UI_MODE_QUALITY) { | |||||
processor_->set_quality((processor_->quality() + 1) & 3); | |||||
SaveState(); | |||||
} else if (mode_ == UI_MODE_PLAYBACK_MODE) { | |||||
uint8_t mode = (processor_->playback_mode() + 1) & 3; | |||||
processor_->set_playback_mode(static_cast<PlaybackMode>(mode)); | |||||
SaveState(); | |||||
} else if (mode_ == UI_MODE_SAVE || mode_ == UI_MODE_LOAD) { | |||||
load_save_location_ = (load_save_location_ + 1) & 3; | |||||
} else { | |||||
mode_ = UI_MODE_VU_METER; | |||||
} | |||||
break; | |||||
case SWITCH_WRITE: | |||||
if (e.data >= kLongPressDuration && switches_.pressed(SWITCH_MODE)) { | |||||
press_time_[SWITCH_MODE] = 0; | |||||
mode_ = UI_MODE_CALIBRATION_1; | |||||
} else if (mode_ == UI_MODE_CALIBRATION_1) { | |||||
CalibrateC1(); | |||||
} else if (mode_ == UI_MODE_CALIBRATION_2) { | |||||
CalibrateC3(); | |||||
} else if (mode_ == UI_MODE_SAVE) { | |||||
// Get pointers on data chunks to save. | |||||
PersistentBlock blocks[4]; | |||||
size_t num_blocks = 0; | |||||
mode_ = UI_MODE_SAVING; | |||||
// Silence the processor during the long erase/write. | |||||
processor_->set_silence(true); | |||||
system_clock.Delay(5); | |||||
processor_->PreparePersistentData(); | |||||
processor_->GetPersistentData(blocks, &num_blocks); | |||||
settings_->SaveSampleMemory(load_save_location_, blocks, num_blocks); | |||||
processor_->set_silence(false); | |||||
load_save_location_ = (load_save_location_ + 1) & 3; | |||||
mode_ = UI_MODE_VU_METER; | |||||
} else if (mode_ == UI_MODE_LOAD) { | |||||
processor_->LoadPersistentData(settings_->sample_flash_data( | |||||
load_save_location_)); | |||||
load_save_location_ = (load_save_location_ + 1) & 3; | |||||
mode_ = UI_MODE_VU_METER; | |||||
} else if (e.data >= kLongPressDuration) { | |||||
mode_ = UI_MODE_SAVE; | |||||
} else { | |||||
mode_ = UI_MODE_LOAD; | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
void Ui::DoEvents() { | |||||
while (queue_.available()) { | |||||
Event e = queue_.PullEvent(); | |||||
if (e.control_type == CONTROL_SWITCH) { | |||||
if (e.data == 0) { | |||||
OnSwitchPressed(e); | |||||
} else { | |||||
if (e.data >= kLongPressDuration && | |||||
e.control_id == SWITCH_MODE && | |||||
switches_.pressed(SWITCH_WRITE)) { | |||||
press_time_[SWITCH_WRITE] = 0; | |||||
OnSecretHandshake(); | |||||
} else { | |||||
OnSwitchReleased(e); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
if (queue_.idle_time() > 1000 && mode_ == UI_MODE_PANIC) { | |||||
queue_.Touch(); | |||||
mode_ = UI_MODE_VU_METER; | |||||
} | |||||
if ((mode_ == UI_MODE_VU_METER || mode_ == UI_MODE_BLEND_METER || | |||||
mode_ == UI_MODE_BLENDING) && \ | |||||
cv_scaler_->blend_knob_touched()) { | |||||
queue_.Touch(); | |||||
mode_ = UI_MODE_BLEND_METER; | |||||
} | |||||
if (queue_.idle_time() > 3000) { | |||||
queue_.Touch(); | |||||
if (mode_ == UI_MODE_BLENDING || mode_ == UI_MODE_QUALITY || | |||||
mode_ == UI_MODE_PLAYBACK_MODE || mode_ == UI_MODE_SAVE || | |||||
mode_ == UI_MODE_LOAD || mode_ == UI_MODE_BLEND_METER || | |||||
mode_ == UI_MODE_SPLASH) { | |||||
mode_ = UI_MODE_VU_METER; | |||||
} | |||||
} | |||||
} | |||||
uint8_t Ui::HandleFactoryTestingRequest(uint8_t command) { | |||||
uint8_t argument = command & 0x1f; | |||||
command = command >> 5; | |||||
uint8_t reply = 0; | |||||
switch (command) { | |||||
case FACTORY_TESTING_READ_POT: | |||||
case FACTORY_TESTING_READ_CV: | |||||
reply = cv_scaler_->adc_value(argument); | |||||
break; | |||||
case FACTORY_TESTING_READ_GATE: | |||||
if (argument <= 2) { | |||||
return switches_.pressed(argument); | |||||
} else { | |||||
return cv_scaler_->gate(argument - 3); | |||||
} | |||||
break; | |||||
case FACTORY_TESTING_SET_BYPASS: | |||||
processor_->set_bypass(argument); | |||||
break; | |||||
case FACTORY_TESTING_CALIBRATE: | |||||
if (argument == 0) { | |||||
mode_ = UI_MODE_CALIBRATION_1; | |||||
} else if (argument == 1) { | |||||
CalibrateC1(); | |||||
} else { | |||||
CalibrateC3(); | |||||
cv_scaler_->set_blend_parameter(static_cast<BlendParameter>(0)); | |||||
SaveState(); | |||||
} | |||||
break; | |||||
} | |||||
return reply; | |||||
} | |||||
} // namespace clouds |
@@ -0,0 +1,130 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// User interface | |||||
#ifndef CLOUDS_UI_H_ | |||||
#define CLOUDS_UI_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/ui/event_queue.h" | |||||
#include "clouds/drivers/leds.h" | |||||
#include "clouds/drivers/switches.h" | |||||
namespace clouds { | |||||
enum UiMode { | |||||
UI_MODE_VU_METER, | |||||
UI_MODE_BLEND_METER, | |||||
UI_MODE_QUALITY, | |||||
UI_MODE_BLENDING, | |||||
UI_MODE_PLAYBACK_MODE, | |||||
UI_MODE_LOAD, | |||||
UI_MODE_SAVE, | |||||
UI_MODE_PANIC, | |||||
UI_MODE_CALIBRATION_1, | |||||
UI_MODE_CALIBRATION_2, | |||||
UI_MODE_SAVING, | |||||
UI_MODE_SPLASH, | |||||
UI_MODE_LAST | |||||
}; | |||||
enum SwitchIndex { | |||||
SWITCH_MODE, | |||||
SWITCH_WRITE, | |||||
SWITCH_FREEZE | |||||
}; | |||||
enum FactoryTestingCommand { | |||||
FACTORY_TESTING_READ_POT, | |||||
FACTORY_TESTING_READ_CV, | |||||
FACTORY_TESTING_READ_GATE, | |||||
FACTORY_TESTING_SET_BYPASS, | |||||
FACTORY_TESTING_CALIBRATE | |||||
}; | |||||
class GranularProcessor; | |||||
class CvScaler; | |||||
class Meter; | |||||
class Settings; | |||||
class Ui { | |||||
public: | |||||
Ui() { } | |||||
~Ui() { } | |||||
void Init( | |||||
Settings* settings, | |||||
CvScaler* cv_scaler, | |||||
GranularProcessor* processor, | |||||
Meter* meter); | |||||
void Poll(); | |||||
void DoEvents(); | |||||
void FlushEvents(); | |||||
void Panic() { | |||||
mode_ = UI_MODE_PANIC; | |||||
} | |||||
uint8_t HandleFactoryTestingRequest(uint8_t command); | |||||
private: | |||||
void OnSwitchPressed(const stmlib::Event& e); | |||||
void OnSwitchReleased(const stmlib::Event& e); | |||||
void OnSecretHandshake(); | |||||
void CalibrateC1(); | |||||
void CalibrateC3(); | |||||
void SaveState(); | |||||
void PaintLeds(); | |||||
void LoadSampleMemory(); | |||||
void SaveSampleMemory(); | |||||
stmlib::EventQueue<16> queue_; | |||||
Settings* settings_; | |||||
CvScaler* cv_scaler_; | |||||
Leds leds_; | |||||
Switches switches_; | |||||
uint32_t press_time_[kNumSwitches]; | |||||
uint32_t long_press_time_[kNumSwitches]; | |||||
UiMode mode_; | |||||
GranularProcessor* processor_; | |||||
Meter* meter_; | |||||
uint8_t load_save_location_; | |||||
DISALLOW_COPY_AND_ASSIGN(Ui); | |||||
}; | |||||
} // namespace clouds | |||||
#endif // CLOUDS_UI_H_ |
@@ -0,0 +1,58 @@ | |||||
Except when noted otherwise, all code is copyright Olivier Gillet and is | |||||
released under the MIT License with the following notice: | |||||
Copyright 2012 Olivier Gillet. | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in | |||||
all copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
THE SOFTWARE. | |||||
------------------------------------------------------------------------------- | |||||
The code in third_party/STM is from STMicroelectronics, and released with the | |||||
following notice: | |||||
THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS | |||||
WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE TIME. | |||||
AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, | |||||
INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE | |||||
CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING | |||||
INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. | |||||
------------------------------------------------------------------------------- | |||||
The code in programming/serial is from Ivan A-R, and released under the GPL with | |||||
the following notice: | |||||
Author: Ivan A-R <ivan@tuxotronic.org> | |||||
Project page: http://tuxotronic.org/wiki/projects/stm32loader | |||||
This file is part of stm32loader. | |||||
stm32loader is free software; you can redistribute it and/or modify it under | |||||
the terms of the GNU General Public License as published by the Free | |||||
Software Foundation; either version 3, or (at your option) any later | |||||
version. | |||||
stm32loader is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or | |||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |||||
for more details. | |||||
You should have received a copy of the GNU General Public License | |||||
along with stm32loader; see the file COPYING3. If not see | |||||
<http://www.gnu.org/licenses/>. |
@@ -0,0 +1,6 @@ | |||||
Mutable Instruments' project template, makefile, and libraries for STM32F | |||||
projects. | |||||
See LICENSE for licensing information. | |||||
More to come! |
@@ -0,0 +1,213 @@ | |||||
// Copyright 2012 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Stack of currently pressed keys. | |||||
// | |||||
// Currently pressed keys are stored as a linked list. The linked list is used | |||||
// as a LIFO stack to allow monosynth-like behaviour. An example of such | |||||
// behaviour is: | |||||
// player presses and holds C4-> C4 is played. | |||||
// player presses and holds C5 (while holding C4) -> C5 is played. | |||||
// player presses and holds G4 (while holding C4&C5)-> G4 is played. | |||||
// player releases C5 -> G4 is played. | |||||
// player releases G4 -> C4 is played. | |||||
// | |||||
// The nodes used in the linked list are pre-allocated from a pool of N | |||||
// nodes, so the "pointers" (to the root element for example) are not actual | |||||
// pointers, but indices of an element in the pool. | |||||
// | |||||
// Additionally, an array of pointers is stored to allow random access to the | |||||
// n-th note, sorted by ascending order of pitch (for arpeggiation). | |||||
#ifndef STMLIB_ALGORITHMS_NOTE_STACK_H_ | |||||
#define STMLIB_ALGORITHMS_NOTE_STACK_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <cstring> | |||||
namespace stmlib { | |||||
enum NoteStackFlags { | |||||
NOTE_STACK_PRIORITY_LAST, | |||||
NOTE_STACK_PRIORITY_LOW, | |||||
NOTE_STACK_PRIORITY_HIGH, | |||||
NOTE_STACK_FREE_SLOT = 0xff | |||||
}; | |||||
struct NoteEntry { | |||||
uint8_t note; | |||||
uint8_t velocity; | |||||
uint8_t next_ptr; // Base 1. | |||||
}; | |||||
template<uint8_t capacity> | |||||
class NoteStack { | |||||
public: | |||||
NoteStack() { } | |||||
~NoteStack() { } | |||||
void Init() { Clear(); } | |||||
void NoteOn(uint8_t note, uint8_t velocity) { | |||||
// Remove the note from the list first (in case it is already here). | |||||
NoteOff(note); | |||||
// In case of saturation, remove the least recently played note from the | |||||
// stack. | |||||
if (size_ == capacity) { | |||||
uint8_t least_recent_note = 1; | |||||
for (uint8_t i = 1; i <= capacity; ++i) { | |||||
if (pool_[i].next_ptr == 0) { | |||||
least_recent_note = pool_[i].note; | |||||
} | |||||
} | |||||
NoteOff(least_recent_note); | |||||
} | |||||
// Now we are ready to insert the new note. Find a free slot to insert it. | |||||
uint8_t free_slot = 1; | |||||
for (uint8_t i = 1; i <= capacity; ++i) { | |||||
if (pool_[i].note == NOTE_STACK_FREE_SLOT) { | |||||
free_slot = i; | |||||
break; | |||||
} | |||||
} | |||||
pool_[free_slot].next_ptr = root_ptr_; | |||||
pool_[free_slot].note = note; | |||||
pool_[free_slot].velocity = velocity; | |||||
root_ptr_ = free_slot; | |||||
// The last step consists in inserting the note in the sorted list. | |||||
for (uint8_t i = 0; i < size_; ++i) { | |||||
if (pool_[sorted_ptr_[i]].note > note) { | |||||
for (uint8_t j = size_; j > i; --j) { | |||||
sorted_ptr_[j] = sorted_ptr_[j - 1]; | |||||
} | |||||
sorted_ptr_[i] = free_slot; | |||||
free_slot = 0; | |||||
break; | |||||
} | |||||
} | |||||
if (free_slot) { | |||||
sorted_ptr_[size_] = free_slot; | |||||
} | |||||
++size_; | |||||
} | |||||
void NoteOff(uint8_t note) { | |||||
uint8_t current = root_ptr_; | |||||
uint8_t previous = 0; | |||||
while (current) { | |||||
if (pool_[current].note == note) { | |||||
break; | |||||
} | |||||
previous = current; | |||||
current = pool_[current].next_ptr; | |||||
} | |||||
if (current) { | |||||
if (previous) { | |||||
pool_[previous].next_ptr = pool_[current].next_ptr; | |||||
} else { | |||||
root_ptr_ = pool_[current].next_ptr; | |||||
} | |||||
for (uint8_t i = 0; i < size_; ++i) { | |||||
if (sorted_ptr_[i] == current) { | |||||
for (uint8_t j = i; j < size_ - 1; ++j) { | |||||
sorted_ptr_[j] = sorted_ptr_[j + 1]; | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
pool_[current].next_ptr = 0; | |||||
pool_[current].note = NOTE_STACK_FREE_SLOT; | |||||
pool_[current].velocity = 0; | |||||
--size_; | |||||
} | |||||
} | |||||
void Clear() { | |||||
size_ = 0; | |||||
memset(pool_ + 1, 0, sizeof(NoteEntry) * capacity); | |||||
memset(sorted_ptr_ + 1, 0, capacity); | |||||
root_ptr_ = 0; | |||||
for (uint8_t i = 0; i <= capacity; ++i) { | |||||
pool_[i].note = NOTE_STACK_FREE_SLOT; | |||||
} | |||||
} | |||||
uint8_t size() const { return size_; } | |||||
uint8_t max_size() const { return capacity; } | |||||
const NoteEntry& most_recent_note() const { return pool_[root_ptr_]; } | |||||
const NoteEntry& least_recent_note() const { | |||||
uint8_t current = root_ptr_; | |||||
while (current && pool_[current].next_ptr) { | |||||
current = pool_[current].next_ptr; | |||||
} | |||||
return pool_[current]; | |||||
} | |||||
const NoteEntry& played_note(uint8_t index) const { | |||||
uint8_t current = root_ptr_; | |||||
index = size_ - index - 1; | |||||
for (uint8_t i = 0; i < index; ++i) { | |||||
current = pool_[current].next_ptr; | |||||
} | |||||
return pool_[current]; | |||||
} | |||||
const NoteEntry& sorted_note(uint8_t index) const { | |||||
return pool_[sorted_ptr_[index]]; | |||||
} | |||||
const NoteEntry& note(uint8_t index) const { return pool_[index]; } | |||||
NoteEntry* mutable_note(uint8_t index) { return &pool_[index]; } | |||||
const NoteEntry& dummy() const { return pool_[0]; } | |||||
const NoteEntry& note_by_priority(NoteStackFlags priority) { | |||||
if (size() == 0) { | |||||
return dummy(); | |||||
} | |||||
switch (priority) { | |||||
case NOTE_STACK_PRIORITY_LAST: | |||||
return most_recent_note(); | |||||
case NOTE_STACK_PRIORITY_LOW: | |||||
return sorted_note(0); | |||||
case NOTE_STACK_PRIORITY_HIGH: | |||||
return sorted_note(size() - 1); | |||||
default: | |||||
return dummy(); | |||||
} | |||||
} | |||||
private: | |||||
uint8_t size_; | |||||
NoteEntry pool_[capacity + 1]; // First element is a dummy node! | |||||
uint8_t root_ptr_; // Base 1. | |||||
uint8_t sorted_ptr_[capacity + 1]; // Base 1. | |||||
DISALLOW_COPY_AND_ASSIGN(NoteStack); | |||||
}; | |||||
} // namespace stmlib | |||||
#endif // STMLIB_ALGORITHMS_NOTE_STACK_H_ |
@@ -0,0 +1,100 @@ | |||||
// Copyright 2013 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Pattern predictor for synchronization to drum patterns or clocks with swing. | |||||
#ifndef STMLIB_ALGORITHMS_PATTERN_PREDICTOR_H_ | |||||
#define STMLIB_ALGORITHMS_PATTERN_PREDICTOR_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <algorithm> | |||||
#include <cstdlib> | |||||
namespace stmlib { | |||||
template<size_t history_size = 16, uint8_t max_candidate_period = 8> | |||||
class PatternPredictor { | |||||
public: | |||||
PatternPredictor() { } | |||||
void Init() { | |||||
history_pointer_ = 0; | |||||
std::fill(&history_[0], &history_[history_size], 0); | |||||
std::fill( | |||||
&prediction_error_[0], | |||||
&prediction_error_[max_candidate_period + 1], | |||||
0); | |||||
std::fill( | |||||
&predicted_period_[0], | |||||
&predicted_period_[max_candidate_period + 1], | |||||
0); | |||||
} | |||||
uint32_t Predict(int32_t value) { | |||||
history_[history_pointer_] = value; | |||||
int best_period = 0; | |||||
for (int i = 0; i <= max_candidate_period; ++i) { | |||||
int32_t error = abs(predicted_period_[i] - value); | |||||
int32_t delta = error - prediction_error_[i]; | |||||
// Compute LP-ed prediction error. | |||||
if (delta > 0) { | |||||
prediction_error_[i] += delta >> 1; | |||||
} else { | |||||
prediction_error_[i] += delta >> 3; | |||||
} | |||||
if (i == 0) { | |||||
predicted_period_[i] = (value + predicted_period_[i]) >> 1; | |||||
} else { | |||||
uint32_t t = history_pointer_ + 1 + history_size - i; | |||||
predicted_period_[i] = history_[t % history_size]; | |||||
} | |||||
if (prediction_error_[i] < prediction_error_[best_period]) { | |||||
best_period = i; | |||||
} | |||||
} | |||||
history_pointer_ = (history_pointer_ + 1) % history_size; | |||||
return static_cast<uint32_t>(predicted_period_[best_period]); | |||||
} | |||||
private: | |||||
uint32_t history_[history_size]; | |||||
int32_t prediction_error_[max_candidate_period + 1]; | |||||
int32_t predicted_period_[max_candidate_period + 1]; | |||||
uint32_t history_pointer_; | |||||
DISALLOW_COPY_AND_ASSIGN(PatternPredictor); | |||||
}; | |||||
} // namespace stmlib | |||||
#endif // STMLIB_ALGORITHMS_PATTERN_PREDICTOR_H_ |
@@ -0,0 +1,121 @@ | |||||
// Copyright 2012 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// A rather inefficient (lookup and insertion in O(n)) map. Useful for storing | |||||
// very small mappings (say about 16 values). | |||||
#ifndef STMLIB_ALGORITHMS_TINY_MAP_H_ | |||||
#define STMLIB_ALGORITHMS_TINY_MAP_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <cstring> | |||||
namespace stmlib { | |||||
template< | |||||
typename Key, | |||||
typename Value, | |||||
uint8_t capacity, | |||||
uint8_t EMPTY = 0xff> | |||||
class TinyMap { | |||||
public: | |||||
struct Entry { | |||||
Key key; | |||||
Value value; | |||||
}; | |||||
TinyMap() { } | |||||
~TinyMap() { } | |||||
void Init() { | |||||
Clear(); | |||||
} | |||||
void Put(Key key, Value value) { | |||||
Entry* entry = Search(key); | |||||
if (entry == NULL) { | |||||
entry = SearchFreeSlot(); | |||||
} | |||||
if (entry == NULL) { | |||||
entry = &map_[0]; | |||||
} | |||||
entry->key = key; | |||||
entry->value = value; | |||||
// Speed up retrieval for next query, in the very common case that when | |||||
// element is inserted, the next query is for this same element. | |||||
if (value != EMPTY) { | |||||
recent_search_ = entry; | |||||
} else { | |||||
recent_delete_ = entry; | |||||
} | |||||
} | |||||
const Entry* Find(uint8_t key) { | |||||
return Search(key); | |||||
} | |||||
void Clear() { | |||||
memset(map_, EMPTY, capacity * sizeof(Entry)); | |||||
recent_search_ = recent_delete_ = &map_[0]; | |||||
} | |||||
private: | |||||
Entry* Search(Key key) { | |||||
if (recent_search_->key == key) { | |||||
return recent_search_; | |||||
} | |||||
for (uint8_t i = 0; i < capacity; ++i) { | |||||
if (map_[i].key == key) { | |||||
recent_search_ = &map_[i]; | |||||
return recent_search_; | |||||
} | |||||
} | |||||
return NULL; | |||||
} | |||||
Entry* SearchFreeSlot() { | |||||
if (recent_delete_->value == EMPTY) { | |||||
return recent_delete_; | |||||
} | |||||
for (uint8_t i = 0; i < capacity; ++i) { | |||||
if (map_[i].value == EMPTY) { | |||||
recent_delete_ = &map_[i]; | |||||
return recent_delete_; | |||||
} | |||||
} | |||||
return NULL; | |||||
} | |||||
Entry map_[capacity]; | |||||
Entry* recent_search_; | |||||
Entry* recent_delete_; | |||||
DISALLOW_COPY_AND_ASSIGN(TinyMap); | |||||
}; | |||||
} // namespace stmlib | |||||
#endif // STMLIB_ALGORITHMS_TINY_MAP_H_ |
@@ -0,0 +1,145 @@ | |||||
// Copyright 2012 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Polyphonic voice allocator. | |||||
#ifndef STMLIB_ALGORITHMS_VOICE_ALLOCATOR_H_ | |||||
#define STMLIB_ALGORITHMS_VOICE_ALLOCATOR_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <cstring> | |||||
namespace stmlib { | |||||
enum VoiceAllocatorFlags { | |||||
NOT_ALLOCATED = 0xff, | |||||
ACTIVE_NOTE = 0x80 | |||||
}; | |||||
template<uint8_t capacity> | |||||
class VoiceAllocator { | |||||
public: | |||||
VoiceAllocator() { } | |||||
void Init() { | |||||
size_ = 0; | |||||
Clear(); | |||||
} | |||||
uint8_t NoteOn(uint8_t note) { | |||||
if (size_ == 0) { | |||||
return NOT_ALLOCATED; | |||||
} | |||||
// First, check if there is a voice currently playing this note. In this | |||||
// case, this voice will be responsible for retriggering this note. | |||||
// Hint: if you're more into string instruments than keyboard instruments, | |||||
// you can safely comment those lines. | |||||
uint8_t voice = Find(note); | |||||
// Then, try to find the least recently touched, currently inactive voice. | |||||
if (voice == NOT_ALLOCATED) { | |||||
for (uint8_t i = 0; i < capacity; ++i) { | |||||
if (lru_[i] < size_ && !(pool_[lru_[i]] & ACTIVE_NOTE)) { | |||||
voice = lru_[i]; | |||||
} | |||||
} | |||||
} | |||||
// If all voices are active, use the least recently played note | |||||
// (voice-stealing). | |||||
if (voice == NOT_ALLOCATED) { | |||||
for (uint8_t i = 0; i < capacity; ++i) { | |||||
if (lru_[i] < size_) { | |||||
voice = lru_[i]; | |||||
} | |||||
} | |||||
} | |||||
pool_[voice] = note | ACTIVE_NOTE; | |||||
Touch(voice); | |||||
return voice; | |||||
} | |||||
uint8_t NoteOff(uint8_t note) { | |||||
uint8_t voice = Find(note); | |||||
if (voice != NOT_ALLOCATED) { | |||||
pool_[voice] &= 0x7f; | |||||
Touch(voice); | |||||
} | |||||
return voice; | |||||
} | |||||
uint8_t Find(uint8_t note) const { | |||||
for (uint8_t i = 0; i < size_; ++i) { | |||||
if ((pool_[i] & 0x7f) == note) { | |||||
return i; | |||||
} | |||||
} | |||||
return NOT_ALLOCATED; | |||||
} | |||||
void Clear() { | |||||
memset(&pool_, 0, sizeof(pool_)); | |||||
for (uint8_t i = 0; i < capacity; ++i) { | |||||
lru_[i] = capacity - i - 1; | |||||
} | |||||
} | |||||
inline void ClearNotes() { | |||||
for (uint8_t i = 0; i < capacity; ++i) { | |||||
pool_[i] &= 0x7f; | |||||
} | |||||
} | |||||
inline void set_size(uint8_t size) { | |||||
size_ = size; | |||||
} | |||||
inline uint8_t size() const { return size_; } | |||||
private: | |||||
void Touch(uint8_t voice) { | |||||
int8_t source = capacity - 1; | |||||
int8_t destination = capacity - 1; | |||||
while (source >= 0) { | |||||
if (lru_[source] != voice) { | |||||
lru_[destination--] = lru_[source]; | |||||
} | |||||
--source; | |||||
} | |||||
lru_[0] = voice; | |||||
} | |||||
uint8_t pool_[capacity]; | |||||
// Holds the indices of the voices sorted by most recent usage. | |||||
uint8_t lru_[capacity]; | |||||
uint8_t size_; | |||||
DISALLOW_COPY_AND_ASSIGN(VoiceAllocator); | |||||
}; | |||||
} // namespace stmlib | |||||
#endif // STMLIB_ALGORITHMS_VOICE_ALLOCATOR_H_ |
@@ -0,0 +1,114 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Fast arc-tangent routines. | |||||
#include "stmlib/dsp/atan.h" | |||||
namespace stmlib { | |||||
/* extern */ | |||||
const uint16_t atan_lut[513] = { | |||||
0, 20, 40, 61, 81, 101, 122, 142, | |||||
162, 183, 203, 224, 244, 264, 285, 305, | |||||
326, 346, 366, 387, 407, 427, 448, 468, | |||||
489, 509, 529, 550, 570, 591, 611, 631, | |||||
652, 672, 693, 713, 733, 754, 774, 795, | |||||
815, 836, 856, 877, 897, 917, 938, 958, | |||||
979, 999, 1020, 1040, 1061, 1081, 1102, 1122, | |||||
1143, 1163, 1184, 1204, 1225, 1245, 1266, 1286, | |||||
1307, 1327, 1348, 1368, 1389, 1409, 1430, 1451, | |||||
1471, 1492, 1512, 1533, 1554, 1574, 1595, 1615, | |||||
1636, 1657, 1677, 1698, 1719, 1739, 1760, 1780, | |||||
1801, 1822, 1843, 1863, 1884, 1905, 1925, 1946, | |||||
1967, 1988, 2008, 2029, 2050, 2071, 2091, 2112, | |||||
2133, 2154, 2175, 2195, 2216, 2237, 2258, 2279, | |||||
2300, 2321, 2342, 2362, 2383, 2404, 2425, 2446, | |||||
2467, 2488, 2509, 2530, 2551, 2572, 2593, 2614, | |||||
2635, 2656, 2677, 2698, 2719, 2740, 2761, 2783, | |||||
2804, 2825, 2846, 2867, 2888, 2910, 2931, 2952, | |||||
2973, 2994, 3016, 3037, 3058, 3079, 3101, 3122, | |||||
3143, 3165, 3186, 3207, 3229, 3250, 3272, 3293, | |||||
3315, 3336, 3357, 3379, 3400, 3422, 3443, 3465, | |||||
3487, 3508, 3530, 3551, 3573, 3595, 3616, 3638, | |||||
3660, 3681, 3703, 3725, 3747, 3768, 3790, 3812, | |||||
3834, 3856, 3877, 3899, 3921, 3943, 3965, 3987, | |||||
4009, 4031, 4053, 4075, 4097, 4119, 4141, 4163, | |||||
4185, 4207, 4230, 4252, 4274, 4296, 4318, 4341, | |||||
4363, 4385, 4408, 4430, 4452, 4475, 4497, 4520, | |||||
4542, 4565, 4587, 4610, 4632, 4655, 4677, 4700, | |||||
4723, 4745, 4768, 4791, 4813, 4836, 4859, 4882, | |||||
4905, 4927, 4950, 4973, 4996, 5019, 5042, 5065, | |||||
5088, 5111, 5134, 5158, 5181, 5204, 5227, 5250, | |||||
5274, 5297, 5320, 5344, 5367, 5390, 5414, 5437, | |||||
5461, 5484, 5508, 5532, 5555, 5579, 5603, 5626, | |||||
5650, 5674, 5698, 5721, 5745, 5769, 5793, 5817, | |||||
5841, 5865, 5889, 5914, 5938, 5962, 5986, 6010, | |||||
6035, 6059, 6084, 6108, 6132, 6157, 6181, 6206, | |||||
6231, 6255, 6280, 6305, 6330, 6354, 6379, 6404, | |||||
6429, 6454, 6479, 6504, 6529, 6554, 6580, 6605, | |||||
6630, 6656, 6681, 6706, 6732, 6757, 6783, 6809, | |||||
6834, 6860, 6886, 6912, 6937, 6963, 6989, 7015, | |||||
7041, 7068, 7094, 7120, 7146, 7173, 7199, 7225, | |||||
7252, 7278, 7305, 7332, 7358, 7385, 7412, 7439, | |||||
7466, 7493, 7520, 7547, 7574, 7602, 7629, 7656, | |||||
7684, 7711, 7739, 7767, 7795, 7822, 7850, 7878, | |||||
7906, 7934, 7962, 7991, 8019, 8047, 8076, 8104, | |||||
8133, 8162, 8190, 8219, 8248, 8277, 8306, 8335, | |||||
8365, 8394, 8423, 8453, 8483, 8512, 8542, 8572, | |||||
8602, 8632, 8662, 8692, 8723, 8753, 8784, 8814, | |||||
8845, 8876, 8907, 8938, 8969, 9000, 9032, 9063, | |||||
9095, 9127, 9158, 9190, 9223, 9255, 9287, 9319, | |||||
9352, 9385, 9418, 9451, 9484, 9517, 9550, 9584, | |||||
9617, 9651, 9685, 9719, 9753, 9788, 9822, 9857, | |||||
9892, 9927, 9962, 9998, 10033, 10069, 10105, 10141, | |||||
10177, 10213, 10250, 10287, 10324, 10361, 10399, 10436, | |||||
10474, 10512, 10550, 10589, 10628, 10667, 10706, 10745, | |||||
10785, 10825, 10865, 10906, 10946, 10988, 11029, 11070, | |||||
11112, 11155, 11197, 11240, 11283, 11327, 11371, 11415, | |||||
11460, 11505, 11550, 11596, 11642, 11688, 11736, 11783, | |||||
11831, 11879, 11928, 11978, 12028, 12078, 12129, 12181, | |||||
12233, 12286, 12340, 12394, 12449, 12505, 12561, 12618, | |||||
12676, 12735, 12795, 12856, 12918, 12981, 13045, 13111, | |||||
13177, 13245, 13315, 13386, 13459, 13533, 13610, 13688, | |||||
13769, 13853, 13939, 14028, 14121, 14218, 14319, 14425, | |||||
14537, 14657, 14785, 14925, 15079, 15254, 15461, 15731, | |||||
16383, | |||||
}; | |||||
// Generated with: | |||||
// static void init_atan_lut() { | |||||
// for (size_t i = 0; i < 513; ++i) { | |||||
// atan_lut[i] = 65536.0 / (2 * M_PI) * asinf(i / 512.0f); | |||||
// printf("%5d, ", atan_lut[i]); | |||||
// if (i % 8 == 7) { | |||||
// printf("\n"); | |||||
// } | |||||
// } | |||||
// printf("\n"); | |||||
// } | |||||
} // namespace stmlib |
@@ -0,0 +1,84 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Fast arc-tangent routines. | |||||
#ifndef STMLIB_DSP_ATAN_H_ | |||||
#define STMLIB_DSP_ATAN_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/dsp/rsqrt.h" | |||||
#include <cmath> | |||||
namespace stmlib { | |||||
static inline uint16_t fast_atan2(float y, float x) { | |||||
static const uint32_t sign_mask = 0x80000000; | |||||
static const float b = 0.596227f; | |||||
uint32_t ux_s = sign_mask & unsafe_bit_cast<uint32_t, float>(x); | |||||
uint32_t uy_s = sign_mask & unsafe_bit_cast<uint32_t, float>(y); | |||||
uint32_t offset = ((~ux_s & uy_s) >> 29 | ux_s >> 30) << 14; | |||||
float bxy_a = fabs(b * x * y); | |||||
float num = bxy_a + y * y; | |||||
float atan_1q = num / (x * x + bxy_a + num); | |||||
uint32_t uatan_2q = (ux_s ^ uy_s) | unsafe_bit_cast<uint32_t, float>(atan_1q); | |||||
return unsafe_bit_cast<float, uint32_t>(uatan_2q) * 16384 + offset; | |||||
} | |||||
extern const uint16_t atan_lut[513]; | |||||
static inline uint16_t fast_atan2r(float y, float x, float* r) { | |||||
float squared_magnitude = x * x + y * y; | |||||
if (squared_magnitude == 0.0f) { | |||||
*r = 0.0f; | |||||
return 0.0f; | |||||
} | |||||
float rinv = fast_rsqrt_carmack(squared_magnitude); | |||||
*r = rinv * squared_magnitude; | |||||
static const uint32_t sign_mask = 0x80000000; | |||||
uint32_t ux_s = sign_mask & unsafe_bit_cast<uint32_t, float>(x); | |||||
uint32_t uy_s = sign_mask & unsafe_bit_cast<uint32_t, float>(y); | |||||
uint32_t quadrant = ((~ux_s & uy_s) >> 29 | ux_s >> 30); | |||||
uint16_t angle = 0; | |||||
x = fabs(x); | |||||
y = fabs(y); | |||||
if (y > x) { | |||||
angle = 16384 - atan_lut[static_cast<uint32_t>(x * rinv * 512.0f + 0.5f)]; | |||||
} else { | |||||
angle = atan_lut[static_cast<uint32_t>(y * rinv * 512.0f + 0.5f)]; | |||||
} | |||||
if (ux_s ^ uy_s) { | |||||
angle = -angle; | |||||
} | |||||
return angle + (quadrant << 14); | |||||
} | |||||
} // namespace stmlib | |||||
#endif // STMLIB_DSP_ATAN_H_ |
@@ -0,0 +1,111 @@ | |||||
import numpy | |||||
import pylab | |||||
def dumb(f): | |||||
return f * numpy.pi | |||||
def pade(f): | |||||
return f * -0.90585 / (-0.28833 + f * f) | |||||
def poly3taylor(f): | |||||
fsq = f * f | |||||
r = 0.3333333 * numpy.pi ** 3 | |||||
r *= fsq | |||||
r += numpy.pi | |||||
r *= f | |||||
return r | |||||
def poly3gradient(f, a=3.736e-01): | |||||
fsq = f * f | |||||
r = a * numpy.pi ** 3 | |||||
r *= fsq | |||||
r += numpy.pi | |||||
r *= f | |||||
return r | |||||
def poly5mdsp(f, a=3.1755e-01, b=2.033e-01): | |||||
fsq = f * f | |||||
r = b * numpy.pi ** 5 | |||||
r *= fsq | |||||
r += a * numpy.pi ** 3 | |||||
r *= fsq | |||||
r += numpy.pi | |||||
r *= f | |||||
return r | |||||
def poly5gradient(f, a=3.260e-01, b=1.823e-01): | |||||
f = f * numpy.pi | |||||
fsq = f * f | |||||
r = b | |||||
r *= fsq | |||||
r += a | |||||
r *= fsq | |||||
r += 1.0 | |||||
r *= f | |||||
return r | |||||
def poly11mdsp(f): | |||||
fsq = f * f | |||||
r = 9.5168091e-03 * numpy.pi ** 11 | |||||
r *= fsq | |||||
r += 2.900525e-03 * numpy.pi ** 9 | |||||
r *= fsq | |||||
r += 5.33740603e-02 * numpy.pi **7 | |||||
r *= fsq | |||||
r += 1.333923995e-01 * numpy.pi **5 | |||||
r *= fsq | |||||
r += 3.333314036e-01 * numpy.pi **3 | |||||
r *= fsq | |||||
r += numpy.pi | |||||
r *= f | |||||
return r | |||||
def compute_filter_settings(cutoff, resonance): | |||||
g = numpy.tan(numpy.pi * cutoff) + resonance * 0 | |||||
r = 1.0 / resonance + cutoff * 0 | |||||
h = 1 / (1 + r * g + g * g) | |||||
return g, r, h | |||||
def evaluate(groundtruth_f, approximate_g): | |||||
approximate_f = numpy.arctan(approximate_g) / numpy.pi | |||||
return numpy.log2(approximate_f / groundtruth_f) * 1200.0 | |||||
f = numpy.exp(numpy.linspace(numpy.log(16), numpy.log(10000), 1000.0)) | |||||
f /= 48000.0 | |||||
g, _, _ = compute_filter_settings(f, 0.5) | |||||
approximations = [pade, poly3gradient, poly5mdsp, poly5gradient, poly11mdsp] | |||||
# | |||||
# a_ = numpy.linspace(3.259e-01, 3.261e-01, 100) | |||||
# b_ = numpy.linspace(1.822e-01, 1.823e-01, 100) | |||||
# error = numpy.zeros((100, 100)) | |||||
# best = 1e8 | |||||
# arg_best = None | |||||
# for i, a in enumerate(a_): | |||||
# for j, b in enumerate(b_): | |||||
# error[i, j] = (evaluate(f, fast0(f, a, b)) ** 2).sum() | |||||
# if error[i, j] < best: | |||||
# best = error[i, j] | |||||
# arg_best = (a, b) | |||||
# | |||||
# print arg_best | |||||
# pylab.plot(coefficient, error) | |||||
# pylab.show() | |||||
pylab.figure(figsize=(15,10)) | |||||
for i, a in enumerate(approximations): | |||||
n = len(approximations) | |||||
# pylab.subplot(n * 100 + 10 + i + 1) | |||||
pylab.plot(f * 48000, evaluate(f, a(f))) | |||||
pylab.xlabel('Hz') | |||||
pylab.ylabel('$\delta$ cents') | |||||
pylab.legend(map(lambda x: x.__name__, approximations)) | |||||
pylab.tight_layout() | |||||
#pylab.savefig('plot.pdf') | |||||
pylab.show() |
@@ -0,0 +1,103 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Cosine oscillator. Generates a cosine between 0.0 and 1.0 with minimal | |||||
// CPU use. | |||||
#ifndef STMLIB_DSP_COSINE_OSCILLATOR_H_ | |||||
#define STMLIB_DSP_COSINE_OSCILLATOR_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <cmath> | |||||
namespace stmlib { | |||||
enum CosineOscillatorMode { | |||||
COSINE_OSCILLATOR_APPROXIMATE, | |||||
COSINE_OSCILLATOR_EXACT | |||||
}; | |||||
class CosineOscillator { | |||||
public: | |||||
CosineOscillator() { } | |||||
~CosineOscillator() { } | |||||
template<CosineOscillatorMode mode> | |||||
inline void Init(float frequency) { | |||||
if (mode == COSINE_OSCILLATOR_APPROXIMATE) { | |||||
InitApproximate(frequency); | |||||
} else { | |||||
iir_coefficient_ = 2.0f * cosf(2.0f * M_PI * frequency); | |||||
initial_amplitude_ = iir_coefficient_ * 0.25f; | |||||
} | |||||
Start(); | |||||
} | |||||
inline void InitApproximate(float frequency) { | |||||
float sign = 16.0f; | |||||
frequency -= 0.25f; | |||||
if (frequency < 0.0f) { | |||||
frequency = -frequency; | |||||
} else { | |||||
if (frequency > 0.5f) { | |||||
frequency -= 0.5f; | |||||
} else { | |||||
sign = -16.0f; | |||||
} | |||||
} | |||||
iir_coefficient_ = sign * frequency * (1.0f - 2.0f * frequency); | |||||
initial_amplitude_ = iir_coefficient_ * 0.25f; | |||||
} | |||||
inline void Start() { | |||||
y1_ = initial_amplitude_; | |||||
y0_ = 0.5f; | |||||
} | |||||
inline float value() const { | |||||
return y1_ + 0.5f; | |||||
} | |||||
inline float Next() { | |||||
float temp = y0_; | |||||
y0_ = iir_coefficient_ * y0_ - y1_; | |||||
y1_ = temp; | |||||
return temp + 0.5f; | |||||
} | |||||
private: | |||||
float y1_; | |||||
float y0_; | |||||
float iir_coefficient_; | |||||
float initial_amplitude_; | |||||
DISALLOW_COPY_AND_ASSIGN(CosineOscillator); | |||||
}; | |||||
} // namespace stmlib | |||||
#endif // STMLIB_DSP_COSINE_OSCILLATOR_H_ |
@@ -0,0 +1,121 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Delay line. | |||||
#ifndef STMLIB_DSP_DELAY_LINE_H_ | |||||
#define STMLIB_DSP_DELAY_LINE_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include "stmlib/dsp/dsp.h" | |||||
#include <algorithm> | |||||
namespace stmlib { | |||||
template<typename T, size_t max_delay> | |||||
class DelayLine { | |||||
public: | |||||
DelayLine() { } | |||||
~DelayLine() { } | |||||
void Init() { | |||||
Reset(); | |||||
} | |||||
void Reset() { | |||||
std::fill(&line_[0], &line_[max_delay], T(0)); | |||||
delay_ = 1; | |||||
write_ptr_ = 0; | |||||
} | |||||
inline void set_delay(size_t delay) { | |||||
delay_ = delay; | |||||
} | |||||
inline void Write(const T sample) { | |||||
line_[write_ptr_] = sample; | |||||
write_ptr_ = (write_ptr_ - 1 + max_delay) % max_delay; | |||||
} | |||||
inline const T Allpass(const T sample, size_t delay, const T coefficient) { | |||||
T read = line_[(write_ptr_ + delay) % max_delay]; | |||||
T write = sample + coefficient * read; | |||||
Write(write); | |||||
return -write * coefficient + read; | |||||
} | |||||
inline const T WriteRead(const T sample, float delay) { | |||||
Write(sample); | |||||
return Read(delay); | |||||
} | |||||
inline const T Read() const { | |||||
return line_[(write_ptr_ + delay_) % max_delay]; | |||||
} | |||||
inline const T Read(size_t delay) const { | |||||
return line_[(write_ptr_ + delay) % max_delay]; | |||||
} | |||||
inline const T Read(float delay) const { | |||||
MAKE_INTEGRAL_FRACTIONAL(delay) | |||||
const T a = line_[(write_ptr_ + delay_integral) % max_delay]; | |||||
const T b = line_[(write_ptr_ + delay_integral + 1) % max_delay]; | |||||
return a + (b - a) * delay_fractional; | |||||
} | |||||
inline const T ReadHermite(float delay) const { | |||||
MAKE_INTEGRAL_FRACTIONAL(delay) | |||||
int32_t t = (write_ptr_ + delay_integral + max_delay); | |||||
const T xm1 = line_[(t - 1) % max_delay]; | |||||
const T x0 = line_[(t) % max_delay]; | |||||
const T x1 = line_[(t + 1) % max_delay]; | |||||
const T x2 = line_[(t + 2) % max_delay]; | |||||
const float c = (x1 - xm1) * 0.5f; | |||||
const float v = x0 - x1; | |||||
const float w = c + v; | |||||
const float a = w + v + (x2 - x0) * 0.5f; | |||||
const float b_neg = w + a; | |||||
const float f = delay_fractional; | |||||
return (((a * f) - b_neg) * f + c) * f + x0; | |||||
} | |||||
private: | |||||
size_t write_ptr_; | |||||
size_t delay_; | |||||
T line_[max_delay]; | |||||
DISALLOW_COPY_AND_ASSIGN(DelayLine); | |||||
}; | |||||
} // namespace stmlib | |||||
#endif // STMLIB_DSP_DELAY_LINE_H_ | |||||
@@ -0,0 +1,161 @@ | |||||
// Copyright 2012 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// DSP utility routines. | |||||
#ifndef STMLIB_UTILS_DSP_DSP_H_ | |||||
#define STMLIB_UTILS_DSP_DSP_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <cmath> | |||||
#include <math.h> | |||||
namespace stmlib { | |||||
#define MAKE_INTEGRAL_FRACTIONAL(x) \ | |||||
int32_t x ## _integral = static_cast<int32_t>(x); \ | |||||
float x ## _fractional = x - static_cast<float>(x ## _integral); | |||||
inline float Interpolate(const float* table, float index, float size) { | |||||
index *= size; | |||||
MAKE_INTEGRAL_FRACTIONAL(index) | |||||
float a = table[index_integral]; | |||||
float b = table[index_integral + 1]; | |||||
return a + (b - a) * index_fractional; | |||||
} | |||||
inline float InterpolateHermite(const float* table, float index, float size) { | |||||
index *= size; | |||||
MAKE_INTEGRAL_FRACTIONAL(index) | |||||
const float xm1 = table[index_integral - 1]; | |||||
const float x0 = table[index_integral + 0]; | |||||
const float x1 = table[index_integral + 1]; | |||||
const float x2 = table[index_integral + 2]; | |||||
const float c = (x1 - xm1) * 0.5f; | |||||
const float v = x0 - x1; | |||||
const float w = c + v; | |||||
const float a = w + v + (x2 - x0) * 0.5f; | |||||
const float b_neg = w + a; | |||||
const float f = index_fractional; | |||||
return (((a * f) - b_neg) * f + c) * f + x0; | |||||
} | |||||
inline float InterpolateWrap(const float* table, float index, float size) { | |||||
index -= static_cast<float>(static_cast<int32_t>(index)); | |||||
index *= size; | |||||
MAKE_INTEGRAL_FRACTIONAL(index) | |||||
float a = table[index_integral]; | |||||
float b = table[index_integral + 1]; | |||||
return a + (b - a) * index_fractional; | |||||
} | |||||
#define ONE_POLE(out, in, coefficient) out += (coefficient) * ((in) - out); | |||||
#define SLOPE(out, in, positive, negative) { \ | |||||
float error = (in) - out; \ | |||||
out += (error > 0 ? positive : negative) * error; \ | |||||
} | |||||
#define SLEW(out, in, delta) { \ | |||||
float error = (in) - out; \ | |||||
float d = (delta); \ | |||||
if (error > d) { \ | |||||
error = d; \ | |||||
} else if (error < -d) { \ | |||||
error = -d; \ | |||||
} \ | |||||
out += error; \ | |||||
} | |||||
inline float Crossfade(float a, float b, float fade) { | |||||
return a + (b - a) * fade; | |||||
} | |||||
inline float SoftLimit(float x) { | |||||
return x * (27.0f + x * x) / (27.0f + 9.0f * x * x); | |||||
} | |||||
inline float SoftClip(float x) { | |||||
if (x < -3.0f) { | |||||
return -1.0f; | |||||
} else if (x > 3.0f) { | |||||
return 1.0f; | |||||
} else { | |||||
return SoftLimit(x); | |||||
} | |||||
} | |||||
#ifdef TEST | |||||
inline int32_t Clip16(int32_t x) { | |||||
if (x < -32768) { | |||||
return -32768; | |||||
} else if (x > 32767) { | |||||
return 32767; | |||||
} else { | |||||
return x; | |||||
} | |||||
} | |||||
inline uint16_t ClipU16(int32_t x) { | |||||
if (x < 0) { | |||||
return 0; | |||||
} else if (x > 65535) { | |||||
return 65535; | |||||
} else { | |||||
return x; | |||||
} | |||||
} | |||||
#else | |||||
inline int32_t Clip16(int32_t x) { | |||||
int32_t result; | |||||
__asm ("ssat %0, %1, %2" : "=r" (result) : "I" (16), "r" (x) ); | |||||
return result; | |||||
} | |||||
inline uint32_t ClipU16(int32_t x) { | |||||
uint32_t result; | |||||
__asm ("usat %0, %1, %2" : "=r" (result) : "I" (16), "r" (x) ); | |||||
return result; | |||||
} | |||||
#endif | |||||
#ifdef TEST | |||||
inline float Sqrt(float x) { | |||||
return sqrtf(x); | |||||
} | |||||
#else | |||||
inline float Sqrt(float x) { | |||||
float result; | |||||
__asm ("vsqrt.f32 %0, %1" : "=w" (result) : "w" (x) ); | |||||
return result; | |||||
} | |||||
#endif | |||||
inline int16_t SoftConvert(float x) { | |||||
return Clip16(static_cast<int32_t>(SoftLimit(x * 0.5f) * 32768.0f)); | |||||
} | |||||
} // namespace stmlib | |||||
#endif // STMLIB_UTILS_DSP_DSP_H_ |
@@ -0,0 +1,735 @@ | |||||
// Copyright 2014 Olivier Gillet. | |||||
// | |||||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||||
// | |||||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
// of this software and associated documentation files (the "Software"), to deal | |||||
// in the Software without restriction, including without limitation the rights | |||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
// copies of the Software, and to permit persons to whom the Software is | |||||
// furnished to do so, subject to the following conditions: | |||||
// | |||||
// The above copyright notice and this permission notice shall be included in | |||||
// all copies or substantial portions of the Software. | |||||
// | |||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
// THE SOFTWARE. | |||||
// | |||||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||||
// | |||||
// ----------------------------------------------------------------------------- | |||||
// | |||||
// Zero-delay-feedback filters (one pole and SVF). | |||||
// Naive SVF. | |||||
#ifndef STMLIB_DSP_FILTER_H_ | |||||
#define STMLIB_DSP_FILTER_H_ | |||||
#include "stmlib/stmlib.h" | |||||
#include <cmath> | |||||
#include <cstdio> | |||||
namespace stmlib { | |||||
enum FilterMode { | |||||
FILTER_MODE_LOW_PASS, | |||||
FILTER_MODE_BAND_PASS, | |||||
FILTER_MODE_BAND_PASS_NORMALIZED, | |||||
FILTER_MODE_HIGH_PASS | |||||
}; | |||||
enum FrequencyApproximation { | |||||
FREQUENCY_EXACT, | |||||
FREQUENCY_ACCURATE, | |||||
FREQUENCY_FAST, | |||||
FREQUENCY_DIRTY | |||||
}; | |||||
#define M_PI_F float(M_PI) | |||||
#define M_PI_POW_2 M_PI * M_PI | |||||
#define M_PI_POW_3 M_PI_POW_2 * M_PI | |||||
#define M_PI_POW_5 M_PI_POW_3 * M_PI_POW_2 | |||||
#define M_PI_POW_7 M_PI_POW_5 * M_PI_POW_2 | |||||
#define M_PI_POW_9 M_PI_POW_7 * M_PI_POW_2 | |||||
#define M_PI_POW_11 M_PI_POW_9 * M_PI_POW_2 | |||||
class DCBlocker { | |||||
public: | |||||
DCBlocker() { } | |||||
~DCBlocker() { } | |||||
void Init(float pole) { | |||||
x_ = 0.0f; | |||||
y_ = 0.0f; | |||||
pole_ = pole; | |||||
} | |||||
inline void Process(float* in_out, size_t size) { | |||||
float x = x_; | |||||
float y = y_; | |||||
const float pole = pole_; | |||||
while (size--) { | |||||
float old_x = x; | |||||
x = *in_out; | |||||
*in_out++ = y = y * pole + x - old_x; | |||||
} | |||||
x_ = x; | |||||
y_ = y; | |||||
} | |||||
private: | |||||
float pole_; | |||||
float x_; | |||||
float y_; | |||||
}; | |||||
class OnePole { | |||||
public: | |||||
OnePole() { } | |||||
~OnePole() { } | |||||
void Init() { | |||||
set_f<FREQUENCY_DIRTY>(0.01f); | |||||
Reset(); | |||||
} | |||||
void Reset() { | |||||
state_ = 0.0f; | |||||
} | |||||
template<FrequencyApproximation approximation> | |||||
static inline float tan(float f) { | |||||
if (approximation == FREQUENCY_EXACT) { | |||||
// Clip coefficient to about 100. | |||||
f = f < 0.497f ? f : 0.497f; | |||||
return tanf(M_PI * f); | |||||
} else if (approximation == FREQUENCY_DIRTY) { | |||||
// Optimized for frequencies below 8kHz. | |||||
const float a = 3.736e-01 * M_PI_POW_3; | |||||
return f * (M_PI_F + a * f * f); | |||||
} else if (approximation == FREQUENCY_FAST) { | |||||
// The usual tangent approximation uses 3.1755e-01 and 2.033e-01, but | |||||
// the coefficients used here are optimized to minimize error for the | |||||
// 16Hz to 16kHz range, with a sample rate of 48kHz. | |||||
const float a = 3.260e-01 * M_PI_POW_3; | |||||
const float b = 1.823e-01 * M_PI_POW_5; | |||||
float f2 = f * f; | |||||
return f * (M_PI_F + f2 * (a + b * f2)); | |||||
} else if (approximation == FREQUENCY_ACCURATE) { | |||||
// These coefficients don't need to be tweaked for the audio range. | |||||
const float a = 3.333314036e-01 * M_PI_POW_3; | |||||
const float b = 1.333923995e-01 * M_PI_POW_5; | |||||
const float c = 5.33740603e-02 * M_PI_POW_7; | |||||
const float d = 2.900525e-03 * M_PI_POW_9; | |||||
const float e = 9.5168091e-03 * M_PI_POW_11; | |||||
float f2 = f * f; | |||||
return f * (M_PI_F + f2 * (a + f2 * (b + f2 * (c + f2 * (d + f2 * e))))); | |||||
} | |||||
} | |||||
// Set frequency and resonance from true units. Various approximations | |||||
// are available to avoid the cost of tanf. | |||||
template<FrequencyApproximation approximation> | |||||
inline void set_f(float f) { | |||||
g_ = tan<approximation>(f); | |||||
gi_ = 1.0f / (1.0f + g_); | |||||
} | |||||
template<FilterMode mode> | |||||
inline float Process(float in) { | |||||
float lp; | |||||
lp = (g_ * in + state_) * gi_; | |||||
state_ = g_ * (in - lp) + lp; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
return lp; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
return in - lp; | |||||
} else { | |||||
return 0.0f; | |||||
} | |||||
} | |||||
private: | |||||
float g_; | |||||
float gi_; | |||||
float state_; | |||||
DISALLOW_COPY_AND_ASSIGN(OnePole); | |||||
}; | |||||
class Svf { | |||||
public: | |||||
Svf() { } | |||||
~Svf() { } | |||||
void Init() { | |||||
set_f_q<FREQUENCY_DIRTY>(0.01f, 100.0f); | |||||
Reset(); | |||||
} | |||||
void Reset() { | |||||
state_1_ = state_2_ = 0.0f; | |||||
} | |||||
// Copy settings from another filter. | |||||
inline void set(const Svf& f) { | |||||
g_ = f.g(); | |||||
r_ = f.r(); | |||||
h_ = f.h(); | |||||
} | |||||
// Set all parameters from LUT. | |||||
inline void set_g_r_h(float g, float r, float h) { | |||||
g_ = g; | |||||
r_ = r; | |||||
h_ = h; | |||||
} | |||||
// Set frequency and resonance coefficients from LUT, adjust remaining | |||||
// parameter. | |||||
inline void set_g_r(float g, float r) { | |||||
g_ = g; | |||||
r_ = r; | |||||
h_ = 1.0f / (1.0f + r_ * g_ + g_ * g_); | |||||
} | |||||
// Set frequency from LUT, resonance in true units, adjust the rest. | |||||
inline void set_g_q(float g, float resonance) { | |||||
g_ = g; | |||||
r_ = 1.0f / resonance; | |||||
h_ = 1.0f / (1.0f + r_ * g_ + g_ * g_); | |||||
} | |||||
// Set frequency and resonance from true units. Various approximations | |||||
// are available to avoid the cost of tanf. | |||||
template<FrequencyApproximation approximation> | |||||
inline void set_f_q(float f, float resonance) { | |||||
g_ = OnePole::tan<approximation>(f); | |||||
r_ = 1.0f / resonance; | |||||
h_ = 1.0f / (1.0f + r_ * g_ + g_ * g_); | |||||
} | |||||
template<FilterMode mode> | |||||
inline float Process(float in) { | |||||
float hp, bp, lp; | |||||
hp = (in - r_ * state_1_ - g_ * state_1_ - state_2_) * h_; | |||||
bp = g_ * hp + state_1_; | |||||
state_1_ = g_ * hp + bp; | |||||
lp = g_ * bp + state_2_; | |||||
state_2_ = g_ * bp + lp; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
return lp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
return bp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
return bp * r_; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
return hp; | |||||
} | |||||
} | |||||
template<FilterMode mode_1, FilterMode mode_2> | |||||
inline void Process(float in, float* out_1, float* out_2) { | |||||
float hp, bp, lp; | |||||
hp = (in - r_ * state_1_ - g_ * state_1_ - state_2_) * h_; | |||||
bp = g_ * hp + state_1_; | |||||
state_1_ = g_ * hp + bp; | |||||
lp = g_ * bp + state_2_; | |||||
state_2_ = g_ * bp + lp; | |||||
if (mode_1 == FILTER_MODE_LOW_PASS) { | |||||
*out_1 = lp; | |||||
} else if (mode_1 == FILTER_MODE_BAND_PASS) { | |||||
*out_1 = bp; | |||||
} else if (mode_1 == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
*out_1 = bp * r_; | |||||
} else if (mode_1 == FILTER_MODE_HIGH_PASS) { | |||||
*out_1 = hp; | |||||
} | |||||
if (mode_2 == FILTER_MODE_LOW_PASS) { | |||||
*out_2 = lp; | |||||
} else if (mode_2 == FILTER_MODE_BAND_PASS) { | |||||
*out_2 = bp; | |||||
} else if (mode_2 == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
*out_2 = bp * r_; | |||||
} else if (mode_2 == FILTER_MODE_HIGH_PASS) { | |||||
*out_2 = hp; | |||||
} | |||||
} | |||||
template<FilterMode mode> | |||||
inline void Process(const float* in, float* out, size_t size) { | |||||
float hp, bp, lp; | |||||
float state_1 = state_1_; | |||||
float state_2 = state_2_; | |||||
while (size--) { | |||||
hp = (*in - r_ * state_1 - g_ * state_1 - state_2) * h_; | |||||
bp = g_ * hp + state_1; | |||||
state_1 = g_ * hp + bp; | |||||
lp = g_ * bp + state_2; | |||||
state_2 = g_ * bp + lp; | |||||
float value; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
value = lp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
value = bp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
value = bp * r_; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
value = hp; | |||||
} | |||||
*out = value; | |||||
++out; | |||||
++in; | |||||
} | |||||
state_1_ = state_1; | |||||
state_2_ = state_2; | |||||
} | |||||
template<FilterMode mode> | |||||
inline void ProcessAdd(const float* in, float* out, size_t size, float gain) { | |||||
float hp, bp, lp; | |||||
float state_1 = state_1_; | |||||
float state_2 = state_2_; | |||||
while (size--) { | |||||
hp = (*in - r_ * state_1 - g_ * state_1 - state_2) * h_; | |||||
bp = g_ * hp + state_1; | |||||
state_1 = g_ * hp + bp; | |||||
lp = g_ * bp + state_2; | |||||
state_2 = g_ * bp + lp; | |||||
float value; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
value = lp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
value = bp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
value = bp * r_; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
value = hp; | |||||
} | |||||
*out += gain * value; | |||||
++out; | |||||
++in; | |||||
} | |||||
state_1_ = state_1; | |||||
state_2_ = state_2; | |||||
} | |||||
template<FilterMode mode> | |||||
inline void Process(const float* in, float* out, size_t size, size_t stride) { | |||||
float hp, bp, lp; | |||||
float state_1 = state_1_; | |||||
float state_2 = state_2_; | |||||
while (size--) { | |||||
hp = (*in - r_ * state_1 - g_ * state_1 - state_2) * h_; | |||||
bp = g_ * hp + state_1; | |||||
state_1 = g_ * hp + bp; | |||||
lp = g_ * bp + state_2; | |||||
state_2 = g_ * bp + lp; | |||||
float value; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
value = lp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
value = bp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
value = bp * r_; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
value = hp; | |||||
} | |||||
*out = value; | |||||
out += stride; | |||||
in += stride; | |||||
} | |||||
state_1_ = state_1; | |||||
state_2_ = state_2; | |||||
} | |||||
inline void ProcessMultimode( | |||||
const float* in, | |||||
float* out, | |||||
size_t size, | |||||
float mode) { | |||||
float hp, bp, lp; | |||||
float state_1 = state_1_; | |||||
float state_2 = state_2_; | |||||
float hp_gain = mode < 0.5f ? -mode * 2.0f : -2.0f + mode * 2.0f; | |||||
float lp_gain = mode < 0.5f ? 1.0f - mode * 2.0f : 0.0f; | |||||
float bp_gain = mode < 0.5f ? 0.0f : mode * 2.0f - 1.0f; | |||||
while (size--) { | |||||
hp = (*in - r_ * state_1 - g_ * state_1 - state_2) * h_; | |||||
bp = g_ * hp + state_1; | |||||
state_1 = g_ * hp + bp; | |||||
lp = g_ * bp + state_2; | |||||
state_2 = g_ * bp + lp; | |||||
*out = hp_gain * hp + bp_gain * bp + lp_gain * lp; | |||||
++in; | |||||
++out; | |||||
} | |||||
state_1_ = state_1; | |||||
state_2_ = state_2; | |||||
} | |||||
template<FilterMode mode> | |||||
inline void Process( | |||||
const float* in, float* out_1, float* out_2, size_t size, | |||||
float gain_1, float gain_2) { | |||||
float hp, bp, lp; | |||||
float state_1 = state_1_; | |||||
float state_2 = state_2_; | |||||
while (size--) { | |||||
hp = (*in - r_ * state_1 - g_ * state_1 - state_2) * h_; | |||||
bp = g_ * hp + state_1; | |||||
state_1 = g_ * hp + bp; | |||||
lp = g_ * bp + state_2; | |||||
state_2 = g_ * bp + lp; | |||||
float value; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
value = lp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
value = bp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
value = bp * r_; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
value = hp; | |||||
} | |||||
*out_1 += value * gain_1; | |||||
*out_2 += value * gain_2; | |||||
++out_1; | |||||
++out_2; | |||||
++in; | |||||
} | |||||
state_1_ = state_1; | |||||
state_2_ = state_2; | |||||
} | |||||
inline float g() const { return g_; } | |||||
inline float r() const { return r_; } | |||||
inline float h() const { return h_; } | |||||
private: | |||||
float g_; | |||||
float r_; | |||||
float h_; | |||||
float state_1_; | |||||
float state_2_; | |||||
DISALLOW_COPY_AND_ASSIGN(Svf); | |||||
}; | |||||
// Naive Chamberlin SVF. | |||||
class NaiveSvf { | |||||
public: | |||||
NaiveSvf() { } | |||||
~NaiveSvf() { } | |||||
void Init() { | |||||
set_f_q<FREQUENCY_DIRTY>(0.01f, 100.0f); | |||||
Reset(); | |||||
} | |||||
void Reset() { | |||||
lp_ = bp_ = 0.0f; | |||||
} | |||||
// Set frequency and resonance from true units. Various approximations | |||||
// are available to avoid the cost of sinf. | |||||
template<FrequencyApproximation approximation> | |||||
inline void set_f_q(float f, float resonance) { | |||||
f = f < 0.497f ? f : 0.497f; | |||||
if (approximation == FREQUENCY_EXACT) { | |||||
f_ = 2.0f * sinf(M_PI_F * f); | |||||
} else { | |||||
f_ = 2.0f * M_PI_F * f; | |||||
} | |||||
damp_ = 1.0f / resonance; | |||||
} | |||||
template<FilterMode mode> | |||||
inline float Process(float in) { | |||||
float hp, notch, bp_normalized; | |||||
bp_normalized = bp_ * damp_; | |||||
notch = in - bp_normalized; | |||||
lp_ += f_ * bp_; | |||||
hp = notch - lp_; | |||||
bp_ += f_ * hp; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
return lp_; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
return bp_; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
return bp_normalized; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
return hp; | |||||
} | |||||
} | |||||
inline float lp() const { return lp_; } | |||||
inline float bp() const { return bp_; } | |||||
template<FilterMode mode> | |||||
inline void Process(const float* in, float* out, size_t size) { | |||||
float hp, notch, bp_normalized; | |||||
float lp = lp_; | |||||
float bp = bp_; | |||||
while (size--) { | |||||
bp_normalized = bp * damp_; | |||||
notch = *in++ - bp_normalized; | |||||
lp += f_ * bp; | |||||
hp = notch - lp; | |||||
bp += f_ * hp; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
*out++ = lp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
*out++ = bp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
*out++ = bp_normalized; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
*out++ = hp; | |||||
} | |||||
} | |||||
lp_ = lp; | |||||
bp_ = bp; | |||||
} | |||||
inline void Split(const float* in, float* low, float* high, size_t size) { | |||||
float hp, notch, bp_normalized; | |||||
float lp = lp_; | |||||
float bp = bp_; | |||||
while (size--) { | |||||
bp_normalized = bp * damp_; | |||||
notch = *in++ - bp_normalized; | |||||
lp += f_ * bp; | |||||
hp = notch - lp; | |||||
bp += f_ * hp; | |||||
*low++ = lp; | |||||
*high++ = hp; | |||||
} | |||||
lp_ = lp; | |||||
bp_ = bp; | |||||
} | |||||
template<FilterMode mode> | |||||
inline void Process(const float* in, float* out, size_t size, size_t decimate) { | |||||
float hp, notch, bp_normalized; | |||||
float lp = lp_; | |||||
float bp = bp_; | |||||
size_t n = decimate - 1; | |||||
while (size--) { | |||||
bp_normalized = bp * damp_; | |||||
notch = *in++ - bp_normalized; | |||||
lp += f_ * bp; | |||||
hp = notch - lp; | |||||
bp += f_ * hp; | |||||
++n; | |||||
if (n == decimate) { | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
*out++ = lp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
*out++ = bp; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
*out++ = bp_normalized; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
*out++ = hp; | |||||
} | |||||
n = 0; | |||||
} | |||||
} | |||||
lp_ = lp; | |||||
bp_ = bp; | |||||
} | |||||
private: | |||||
float f_; | |||||
float damp_; | |||||
float lp_; | |||||
float bp_; | |||||
DISALLOW_COPY_AND_ASSIGN(NaiveSvf); | |||||
}; | |||||
// Modified Chamberlin SVF (Duane K. Wise) | |||||
// http://www.dafx.ca/proceedings/papers/p_053.pdf | |||||
class ModifiedSvf { | |||||
public: | |||||
ModifiedSvf() { } | |||||
~ModifiedSvf() { } | |||||
void Init() { | |||||
Reset(); | |||||
} | |||||
void Reset() { | |||||
lp_ = bp_ = 0.0f; | |||||
} | |||||
inline void set_f_fq(float f, float fq) { | |||||
f_ = f; | |||||
fq_ = fq; | |||||
x_ = 0.0f; | |||||
} | |||||
template<FilterMode mode> | |||||
inline void Process(const float* in, float* out, size_t size) { | |||||
float lp = lp_; | |||||
float bp = bp_; | |||||
float x = x_; | |||||
const float fq = fq_; | |||||
const float f = f_; | |||||
while (size--) { | |||||
lp += f * bp; | |||||
bp += -fq * bp -f * lp + *in; | |||||
if (mode == FILTER_MODE_BAND_PASS || | |||||
mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
bp += x; | |||||
} | |||||
x = *in++; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
*out++ = lp * f; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
*out++ = bp * f; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
*out++ = bp * fq; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
*out++ = x - lp * f - bp * fq; | |||||
} | |||||
} | |||||
lp_ = lp; | |||||
bp_ = bp; | |||||
x_ = x; | |||||
} | |||||
private: | |||||
float f_; | |||||
float fq_; | |||||
float x_; | |||||
float lp_; | |||||
float bp_; | |||||
DISALLOW_COPY_AND_ASSIGN(ModifiedSvf); | |||||
}; | |||||
// Two passes of modified Chamberlin SVF with the same coefficients - | |||||
// to implement Linkwitz–Riley (Butterworth squared) crossover filters. | |||||
class CrossoverSvf { | |||||
public: | |||||
CrossoverSvf() { } | |||||
~CrossoverSvf() { } | |||||
void Init() { | |||||
Reset(); | |||||
} | |||||
void Reset() { | |||||
lp_[0] = bp_[0] = lp_[1] = bp_[1] = 0.0f; | |||||
x_[0] = 0.0f; | |||||
x_[1] = 0.0f; | |||||
} | |||||
inline void set_f_fq(float f, float fq) { | |||||
f_ = f; | |||||
fq_ = fq; | |||||
} | |||||
template<FilterMode mode> | |||||
inline void Process(const float* in, float* out, size_t size) { | |||||
float lp_1 = lp_[0]; | |||||
float bp_1 = bp_[0]; | |||||
float lp_2 = lp_[1]; | |||||
float bp_2 = bp_[1]; | |||||
float x_1 = x_[0]; | |||||
float x_2 = x_[1]; | |||||
const float fq = fq_; | |||||
const float f = f_; | |||||
while (size--) { | |||||
lp_1 += f * bp_1; | |||||
bp_1 += -fq * bp_1 -f * lp_1 + *in; | |||||
if (mode == FILTER_MODE_BAND_PASS || | |||||
mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
bp_1 += x_1; | |||||
} | |||||
x_1 = *in++; | |||||
float y; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
y = lp_1 * f; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
y = bp_1 * f; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
y = bp_1 * fq; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
y = x_1 - lp_1 * f - bp_1 * fq; | |||||
} | |||||
lp_2 += f * bp_2; | |||||
bp_2 += -fq * bp_2 -f * lp_2 + y; | |||||
if (mode == FILTER_MODE_BAND_PASS || | |||||
mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
bp_2 += x_2; | |||||
} | |||||
x_2 = y; | |||||
if (mode == FILTER_MODE_LOW_PASS) { | |||||
*out++ = lp_2 * f; | |||||
} else if (mode == FILTER_MODE_BAND_PASS) { | |||||
*out++ = bp_2 * f; | |||||
} else if (mode == FILTER_MODE_BAND_PASS_NORMALIZED) { | |||||
*out++ = bp_2 * fq; | |||||
} else if (mode == FILTER_MODE_HIGH_PASS) { | |||||
*out++ = x_2 - lp_2 * f - bp_2 * fq; | |||||
} | |||||
} | |||||
lp_[0] = lp_1; | |||||
bp_[0] = bp_1; | |||||
lp_[1] = lp_2; | |||||
bp_[1] = bp_2; | |||||
x_[0] = x_1; | |||||
x_[1] = x_2; | |||||
} | |||||
private: | |||||
float f_; | |||||
float fq_; | |||||
float x_[2]; | |||||
float lp_[2]; | |||||
float bp_[2]; | |||||
DISALLOW_COPY_AND_ASSIGN(CrossoverSvf); | |||||
}; | |||||
} // namespace stmlib | |||||
#endif // STMLIB_DSP_FILTER_H_ |