This is a part of the Kinetiszer project.
pitch.c@1:8ae4ab73ca6a, 2014-10-28 (annotated)
- Committer:
- Clemo
- Date:
- Tue Oct 28 20:09:12 2014 +0000
- Revision:
- 1:8ae4ab73ca6a
- Parent:
- 0:cb80470434eb
First publication (untested)
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
Clemo | 0:cb80470434eb | 1 | /* |
Clemo | 0:cb80470434eb | 2 | Copyright 2013 Paul Soulsby www.soulsbysynths.com |
Clemo | 0:cb80470434eb | 3 | This file is part of Atmegatron. |
Clemo | 0:cb80470434eb | 4 | |
Clemo | 0:cb80470434eb | 5 | Atmegatron is free software: you can redistribute it and/or modify |
Clemo | 0:cb80470434eb | 6 | it under the terms of the GNU General Public License as published by |
Clemo | 0:cb80470434eb | 7 | the Free Software Foundation, either version 3 of the License, or |
Clemo | 0:cb80470434eb | 8 | (at your option) any later version. |
Clemo | 0:cb80470434eb | 9 | |
Clemo | 0:cb80470434eb | 10 | Atmegatron is distributed in the hope that it will be useful, |
Clemo | 0:cb80470434eb | 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
Clemo | 0:cb80470434eb | 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
Clemo | 0:cb80470434eb | 13 | GNU General Public License for more details. |
Clemo | 0:cb80470434eb | 14 | |
Clemo | 0:cb80470434eb | 15 | You should have received a copy of the GNU General Public License |
Clemo | 0:cb80470434eb | 16 | along with Atmegatron. If not, see <http://www.gnu.org/licenses/>. |
Clemo | 0:cb80470434eb | 17 | */ |
Clemo | 0:cb80470434eb | 18 | |
Clemo | 0:cb80470434eb | 19 | #include "atmegatron.h" |
Clemo | 0:cb80470434eb | 20 | |
Clemo | 0:cb80470434eb | 21 | |
Clemo | 0:cb80470434eb | 22 | //local variables |
Clemo | 0:cb80470434eb | 23 | unsigned long porta_starttick; //portamento - tick glide started |
Clemo | 0:cb80470434eb | 24 | unsigned long porta_curtick; //portamento - current tick through glide |
Clemo | 0:cb80470434eb | 25 | byte pitch_lfolookup[256]; //look up table to convert lfo output to pitch multiplier. Lookup saves having to do v slow exp calculation every cycle |
Clemo | 0:cb80470434eb | 26 | byte pitch_fenvlookup[256]; //look up table to convert env output to pitch multiplier. Lookup saves having to do v slow exp calculation every cycle |
Clemo | 0:cb80470434eb | 27 | byte pitch_pbendlookup; //look up value to convert pitch bend to pitch multiplier. Lookup saves having to do v slow exp calculation every cycle. Don't need table, because it's a fixed value |
Clemo | 0:cb80470434eb | 28 | float pitch_lfoamtf = 0; //lfo amount as float (0-1) |
Clemo | 0:cb80470434eb | 29 | float pitch_fenvamtf = 0; //env amount as float (0-1) |
Clemo | 0:cb80470434eb | 30 | unsigned long pitch_lastfreq_calc = 220; //last value of pitch_freq_calc |
Clemo | 0:cb80470434eb | 31 | char pitch_lastpbend; |
Clemo | 0:cb80470434eb | 32 | boolean pitch_changed = true; |
Clemo | 0:cb80470434eb | 33 | |
Clemo | 0:cb80470434eb | 34 | //lets and gets |
Clemo | 0:cb80470434eb | 35 | unsigned int pitch_lastfreq = 220; //last frequency set |
Clemo | 0:cb80470434eb | 36 | unsigned int pitch_curfreq = 220; //current frequency (must never be 0) |
Clemo | 0:cb80470434eb | 37 | unsigned int pitch_nextfreq = 220; //next frequency. Frequency that current frequency is heading to (based on portamento level) |
Clemo | 0:cb80470434eb | 38 | unsigned int pitch_portaticks = 0; //portamento speed in ticks (ticks to glide from one note to another) |
Clemo | 0:cb80470434eb | 39 | unsigned int pitch_portaspeed = 0; //portamento speed in ticks not taking into account of distance between prev and next notes (propotional mode) |
Clemo | 0:cb80470434eb | 40 | boolean pitch_propporta = false; //portamento propotional mode |
Clemo | 0:cb80470434eb | 41 | byte pitch_lfoamt = 0; //amount lfo effects pitch |
Clemo | 0:cb80470434eb | 42 | byte pitch_fenvamt = 0; //amount env effects pitch |
Clemo | 0:cb80470434eb | 43 | unsigned long pitch_freq_calc = 220; //master current frequency (after lfo, env, pitch wheel calculated) |
Clemo | 0:cb80470434eb | 44 | |
Clemo | 0:cb80470434eb | 45 | |
Clemo | 0:cb80470434eb | 46 | //lets and gets |
Clemo | 0:cb80470434eb | 47 | void Pitch_Let_NextFreq(unsigned int newfreq) |
Clemo | 0:cb80470434eb | 48 | { //set next frequency - i.e. the freq that the portamento heads towards |
Clemo | 0:cb80470434eb | 49 | if (newfreq!=pitch_nextfreq){ //if new value != current, then update and refresh portamento |
Clemo | 0:cb80470434eb | 50 | pitch_nextfreq = newfreq; |
Clemo | 0:cb80470434eb | 51 | pitch_lastfreq = pitch_curfreq; |
Clemo | 0:cb80470434eb | 52 | Pitch_RefreshPortaTicks(); |
Clemo | 0:cb80470434eb | 53 | } |
Clemo | 0:cb80470434eb | 54 | } |
Clemo | 0:cb80470434eb | 55 | |
Clemo | 0:cb80470434eb | 56 | |
Clemo | 0:cb80470434eb | 57 | unsigned int Pitch_Get_NextFreq(void) |
Clemo | 0:cb80470434eb | 58 | { |
Clemo | 0:cb80470434eb | 59 | return pitch_nextfreq; |
Clemo | 0:cb80470434eb | 60 | } |
Clemo | 0:cb80470434eb | 61 | |
Clemo | 0:cb80470434eb | 62 | |
Clemo | 0:cb80470434eb | 63 | void Pitch_Let_Porta(int newporta) |
Clemo | 0:cb80470434eb | 64 | { //set portamento speed. this is a preset value stored in hardware tab. |
Clemo | 0:cb80470434eb | 65 | if (newporta!=pitch_portaspeed){ |
Clemo | 0:cb80470434eb | 66 | pitch_portaspeed = newporta; |
Clemo | 0:cb80470434eb | 67 | Pitch_RefreshPortaTicks(); //if proportional mode is on, the actual porta speed (portaticks) needs calculating. |
Clemo | 0:cb80470434eb | 68 | } |
Clemo | 0:cb80470434eb | 69 | } |
Clemo | 0:cb80470434eb | 70 | |
Clemo | 0:cb80470434eb | 71 | |
Clemo | 0:cb80470434eb | 72 | int Pitch_Get_Porta(void) |
Clemo | 0:cb80470434eb | 73 | { |
Clemo | 0:cb80470434eb | 74 | return pitch_portaspeed; |
Clemo | 0:cb80470434eb | 75 | } |
Clemo | 0:cb80470434eb | 76 | |
Clemo | 0:cb80470434eb | 77 | |
Clemo | 0:cb80470434eb | 78 | void Pitch_ResetPorta(void) |
Clemo | 0:cb80470434eb | 79 | { |
Clemo | 0:cb80470434eb | 80 | porta_starttick = master_tick; |
Clemo | 0:cb80470434eb | 81 | } |
Clemo | 0:cb80470434eb | 82 | |
Clemo | 0:cb80470434eb | 83 | |
Clemo | 0:cb80470434eb | 84 | void Pitch_RefreshPortaTicks(void) |
Clemo | 0:cb80470434eb | 85 | { |
Clemo | 0:cb80470434eb | 86 | unsigned long p; |
Clemo | 0:cb80470434eb | 87 | if (pitch_portaspeed==0){ //if portamento is off, speed = 0 ticks |
Clemo | 0:cb80470434eb | 88 | pitch_portaticks = 0; |
Clemo | 0:cb80470434eb | 89 | } |
Clemo | 0:cb80470434eb | 90 | else if (pitch_propporta==true) { //if propotional mode is on, speeed is proportional to diff between current and next frequencies |
Clemo | 0:cb80470434eb | 91 | if (pitch_nextfreq>pitch_curfreq){ |
Clemo | 0:cb80470434eb | 92 | p = (unsigned long)(pitch_nextfreq-pitch_curfreq) * pitch_portaspeed / pitch_curfreq; |
Clemo | 0:cb80470434eb | 93 | } |
Clemo | 0:cb80470434eb | 94 | else{ |
Clemo | 0:cb80470434eb | 95 | p = (unsigned long)(pitch_curfreq-pitch_nextfreq) * pitch_portaspeed / pitch_nextfreq; |
Clemo | 0:cb80470434eb | 96 | } |
Clemo | 0:cb80470434eb | 97 | pitch_portaticks = (unsigned int)p; |
Clemo | 0:cb80470434eb | 98 | } |
Clemo | 0:cb80470434eb | 99 | else{ |
Clemo | 0:cb80470434eb | 100 | pitch_portaticks = pitch_portaspeed; //if proportional mode is off, speed is just = portaspeed |
Clemo | 0:cb80470434eb | 101 | } |
Clemo | 0:cb80470434eb | 102 | } |
Clemo | 0:cb80470434eb | 103 | |
Clemo | 0:cb80470434eb | 104 | |
Clemo | 0:cb80470434eb | 105 | unsigned long Pitch_Get_FreqCalc(void) |
Clemo | 0:cb80470434eb | 106 | { |
Clemo | 0:cb80470434eb | 107 | return pitch_freq_calc; //needed by interrupt loop to set frequency |
Clemo | 0:cb80470434eb | 108 | } |
Clemo | 0:cb80470434eb | 109 | |
Clemo | 0:cb80470434eb | 110 | |
Clemo | 0:cb80470434eb | 111 | boolean Pitch_Get_PitchChanged(void) |
Clemo | 0:cb80470434eb | 112 | { //has freq changed since last |
Clemo | 0:cb80470434eb | 113 | return pitch_changed; |
Clemo | 0:cb80470434eb | 114 | } |
Clemo | 0:cb80470434eb | 115 | |
Clemo | 0:cb80470434eb | 116 | |
Clemo | 0:cb80470434eb | 117 | void Pitch_Let_PropPorta(boolean newprop) |
Clemo | 0:cb80470434eb | 118 | { //set proportional portamento mode. refresh portaticks. |
Clemo | 0:cb80470434eb | 119 | if (newprop!=pitch_propporta){ |
Clemo | 0:cb80470434eb | 120 | pitch_propporta = newprop; |
Clemo | 0:cb80470434eb | 121 | Pitch_RefreshPortaTicks(); |
Clemo | 0:cb80470434eb | 122 | } |
Clemo | 0:cb80470434eb | 123 | } |
Clemo | 0:cb80470434eb | 124 | |
Clemo | 0:cb80470434eb | 125 | |
Clemo | 0:cb80470434eb | 126 | boolean Pitch_Get_PropPorta(void) |
Clemo | 0:cb80470434eb | 127 | { |
Clemo | 0:cb80470434eb | 128 | return pitch_propporta; |
Clemo | 0:cb80470434eb | 129 | } |
Clemo | 0:cb80470434eb | 130 | |
Clemo | 0:cb80470434eb | 131 | |
Clemo | 0:cb80470434eb | 132 | //************PITCH PROCESS**************** |
Clemo | 0:cb80470434eb | 133 | void Pitch_Process(void) |
Clemo | 0:cb80470434eb | 134 | { |
Clemo | 0:cb80470434eb | 135 | unsigned long p; |
Clemo | 0:cb80470434eb | 136 | |
Clemo | 0:cb80470434eb | 137 | if (pitch_curfreq!=pitch_nextfreq){ //is current frequency = next freq (i.e. the freq we want it to eventually be). first calculate the portamento |
Clemo | 0:cb80470434eb | 138 | if (pitch_portaticks==0){ //if portamento is off |
Clemo | 0:cb80470434eb | 139 | pitch_curfreq = pitch_nextfreq; //curfreq = next freq (no glide) |
Clemo | 0:cb80470434eb | 140 | } |
Clemo | 0:cb80470434eb | 141 | else{ //if portamento is on |
Clemo | 0:cb80470434eb | 142 | porta_curtick = master_tick - porta_starttick; //calc ticks passed |
Clemo | 0:cb80470434eb | 143 | if (porta_curtick>pitch_portaticks){ //curtick > portaticks, curfreq = next freq (i.e. glide has finished) |
Clemo | 0:cb80470434eb | 144 | pitch_curfreq = pitch_nextfreq; |
Clemo | 0:cb80470434eb | 145 | } |
Clemo | 0:cb80470434eb | 146 | else{ //otherwise |
Clemo | 0:cb80470434eb | 147 | pitch_curfreq = map(porta_curtick,0,pitch_portaticks,pitch_lastfreq,pitch_nextfreq); //map current frequency to ticks (uses arduino map function: curtick*(nextfreq - lastfreq) / portticks + lastfreq) |
Clemo | 0:cb80470434eb | 148 | } |
Clemo | 0:cb80470434eb | 149 | } |
Clemo | 0:cb80470434eb | 150 | } |
Clemo | 0:cb80470434eb | 151 | else if (pitch_lastfreq!=pitch_curfreq){ //if curfreq at next freq, but last freq != cur freq, make sure it is. |
Clemo | 0:cb80470434eb | 152 | pitch_lastfreq=pitch_curfreq; |
Clemo | 0:cb80470434eb | 153 | } |
Clemo | 0:cb80470434eb | 154 | |
Clemo | 0:cb80470434eb | 155 | p = (unsigned long)pitch_curfreq; //now mult pitch by lfo, env and pitch wheel |
Clemo | 0:cb80470434eb | 156 | if (pitch_lfoamt>0){ |
Clemo | 0:cb80470434eb | 157 | p = p * (Pitch_Get_LFOGain() + 1) >> PITCH_LFOBS; //mult pitch by lfo gain and bit shift. This is the fixed point maths way of mult pitch by lfo (i.e. not using floating point) |
Clemo | 0:cb80470434eb | 158 | } |
Clemo | 0:cb80470434eb | 159 | if (pitch_fenvamt>0){ |
Clemo | 0:cb80470434eb | 160 | p = p * (Pitch_Get_FenvGain() + 1) >> PITCH_ENVBS; //mult pitch by env gain and bit shift. This is the fixed point maths way of mult pitch by env (i.e. not using floating point) |
Clemo | 0:cb80470434eb | 161 | } |
Clemo | 0:cb80470434eb | 162 | |
Clemo | 0:cb80470434eb | 163 | if (MIDI_Get_PitchBend_Level()!=0){ |
Clemo | 0:cb80470434eb | 164 | p = p * (Pitch_Get_MIDIPbendGain() + 1) >> 7UL; //mult pitch by pbend wheel and bit shift. This is the fixed point maths way of mult pitch by pbend (i.e. not using floating point) |
Clemo | 0:cb80470434eb | 165 | } //pbend is MSB only, so 0-127, hence bit shift by 7 bits |
Clemo | 0:cb80470434eb | 166 | pitch_freq_calc = p; //set pitch_freq_calc, for use in interrupt |
Clemo | 0:cb80470434eb | 167 | |
Clemo | 0:cb80470434eb | 168 | if (pitch_freq_calc!=pitch_lastfreq_calc){ //see if pitch has actually changed since last calculation (to save unneccessary calc in interrupt) |
Clemo | 0:cb80470434eb | 169 | pitch_changed = true; |
Clemo | 0:cb80470434eb | 170 | pitch_lastfreq_calc = pitch_freq_calc; |
Clemo | 0:cb80470434eb | 171 | } |
Clemo | 0:cb80470434eb | 172 | else{ |
Clemo | 0:cb80470434eb | 173 | pitch_changed = false; |
Clemo | 0:cb80470434eb | 174 | } |
Clemo | 0:cb80470434eb | 175 | } |
Clemo | 0:cb80470434eb | 176 | |
Clemo | 0:cb80470434eb | 177 | |
Clemo | 0:cb80470434eb | 178 | //Pitch LFO amount. This is stored as a byte representing knob position and a float (0-PITCH_LFOMAX) |
Clemo | 0:cb80470434eb | 179 | void Pitch_Let_LFOAmt(byte newamt) |
Clemo | 0:cb80470434eb | 180 | { |
Clemo | 0:cb80470434eb | 181 | if (newamt!=pitch_lfoamt){ //if new value different to current |
Clemo | 0:cb80470434eb | 182 | pitch_lfoamt = newamt; //set new value |
Clemo | 0:cb80470434eb | 183 | pitch_lfoamtf = (float)pitch_lfoamt * PITCH_LFOMAX / 255; //calculate new float value used to calculate LFO lookup table (0-PITCH_LFOMAX) |
Clemo | 0:cb80470434eb | 184 | memset(pitch_lfolookup,0,sizeof(pitch_lfolookup)); //clear the lookup table (quicker than loop) |
Clemo | 0:cb80470434eb | 185 | } |
Clemo | 0:cb80470434eb | 186 | } |
Clemo | 0:cb80470434eb | 187 | |
Clemo | 0:cb80470434eb | 188 | |
Clemo | 0:cb80470434eb | 189 | byte Pitch_Get_LFOAmt(void) |
Clemo | 0:cb80470434eb | 190 | { |
Clemo | 0:cb80470434eb | 191 | return pitch_lfoamt; |
Clemo | 0:cb80470434eb | 192 | } |
Clemo | 0:cb80470434eb | 193 | |
Clemo | 0:cb80470434eb | 194 | |
Clemo | 0:cb80470434eb | 195 | //Return the 'gain' of LFO. This is stored in a lookup table to save time consuming calculations each time. See the forums for further explanation of the 'gain' term |
Clemo | 0:cb80470434eb | 196 | byte Pitch_Get_LFOGain(void) |
Clemo | 0:cb80470434eb | 197 | { |
Clemo | 0:cb80470434eb | 198 | byte index; |
Clemo | 0:cb80470434eb | 199 | float f, g; |
Clemo | 0:cb80470434eb | 200 | index = LFO_Get_Level() + 127; //get the current output level of LFO (-127 - 128) and add offset (can't lookup negative array indexes) |
Clemo | 0:cb80470434eb | 201 | if (pitch_lfolookup[index]==0){ //if index of lookup table hasn't yet been calculated: |
Clemo | 0:cb80470434eb | 202 | f = (float)LFO_Get_Level() / 127; //convert LFO output to float (0 - 1) |
Clemo | 0:cb80470434eb | 203 | g = round(exp(f * pitch_lfoamtf) * PITCH_LFOMULT - 1); //calculate lookup table. e.g. lfoamt = 0: exp(0) * 64 - 1 = 63 e.g. lfoamt = 1 and lfo level = 127: exp(ln(4)) * 64 - 1 = 255 e.g. lfoamt = 1 and lfo = -127: exp(-ln(4)) * 64 - 1 = 15 |
Clemo | 0:cb80470434eb | 204 | pitch_lfolookup[index] = (byte)g; //write value to lookup table |
Clemo | 0:cb80470434eb | 205 | } |
Clemo | 0:cb80470434eb | 206 | return pitch_lfolookup[index]; |
Clemo | 0:cb80470434eb | 207 | } |
Clemo | 0:cb80470434eb | 208 | |
Clemo | 0:cb80470434eb | 209 | |
Clemo | 0:cb80470434eb | 210 | //pitch Env amount. This is stored as a byte representing knob position and a float (0-PITCH_ENVMAX) |
Clemo | 0:cb80470434eb | 211 | void Pitch_Let_FenvAmt(byte newamt) |
Clemo | 0:cb80470434eb | 212 | { |
Clemo | 0:cb80470434eb | 213 | if (newamt!=pitch_fenvamt){ //if new value different to current |
Clemo | 0:cb80470434eb | 214 | pitch_fenvamt = newamt; //set new value |
Clemo | 0:cb80470434eb | 215 | pitch_fenvamtf = (float)pitch_fenvamt * PITCH_ENVMAX / 255;//calculate new float value used to calculate env lookup table (0-PITCH_ENVMAX) |
Clemo | 0:cb80470434eb | 216 | memset(pitch_fenvlookup,0,sizeof(pitch_fenvlookup)); //clear the lookup table (quicker than loop) |
Clemo | 0:cb80470434eb | 217 | } |
Clemo | 0:cb80470434eb | 218 | } |
Clemo | 0:cb80470434eb | 219 | |
Clemo | 0:cb80470434eb | 220 | |
Clemo | 0:cb80470434eb | 221 | byte Pitch_Get_FenvAmt(void) |
Clemo | 0:cb80470434eb | 222 | { |
Clemo | 0:cb80470434eb | 223 | return pitch_fenvamt; |
Clemo | 0:cb80470434eb | 224 | } |
Clemo | 0:cb80470434eb | 225 | |
Clemo | 0:cb80470434eb | 226 | |
Clemo | 0:cb80470434eb | 227 | //Return the 'gain' of env. This is stored in a lookup table to save time consuming calculations each time. See the forums for further explanation of the 'gain' term |
Clemo | 0:cb80470434eb | 228 | byte Pitch_Get_FenvGain(void) |
Clemo | 0:cb80470434eb | 229 | { |
Clemo | 0:cb80470434eb | 230 | byte index; |
Clemo | 0:cb80470434eb | 231 | float f, g; |
Clemo | 0:cb80470434eb | 232 | index = Fenv_Get_Level() + 127; //get the current output level of env (-127 - 128) and add offset (can't lookup negative array indexes) |
Clemo | 0:cb80470434eb | 233 | if (pitch_fenvlookup[index]==0){ //if index of lookup table hasn't yet been calculated: |
Clemo | 0:cb80470434eb | 234 | f = (float)Fenv_Get_Level() / 127; //convert env output to float (0 - 1) |
Clemo | 0:cb80470434eb | 235 | g = round(exp(f * pitch_fenvamtf) * PITCH_ENVMULT - 1); //calculate lookup table. e.g. envamt = 0: exp(0) * 64 - 1 = 63 e.g. envamt = 1 and lfo level = 127: exp(ln(4)) * 64 - 1 = 255 e.g. envamt = 1 and env = -127: exp(-ln(4)) * 64 - 1 = 15 |
Clemo | 0:cb80470434eb | 236 | pitch_fenvlookup[index] = (byte)g; //write value to lookup table |
Clemo | 0:cb80470434eb | 237 | } |
Clemo | 0:cb80470434eb | 238 | return pitch_fenvlookup[index]; |
Clemo | 0:cb80470434eb | 239 | } |
Clemo | 0:cb80470434eb | 240 | |
Clemo | 0:cb80470434eb | 241 | |
Clemo | 0:cb80470434eb | 242 | //Return the 'gain' of the pitch bend. This does not require a lookup table, as it's a single value (rather than waveform or env shape) |
Clemo | 0:cb80470434eb | 243 | byte Pitch_Get_MIDIPbendGain(void) |
Clemo | 0:cb80470434eb | 244 | { |
Clemo | 0:cb80470434eb | 245 | float f, g; |
Clemo | 0:cb80470434eb | 246 | if (MIDI_Get_PitchBend_Level()!=pitch_lastpbend){ //has pitchbend value actually changed? |
Clemo | 0:cb80470434eb | 247 | f = (float)MIDI_Get_PitchBend_Level() / 63; //convert to floating point. |
Clemo | 0:cb80470434eb | 248 | g = round(exp(f * PITCH_LFOMAX) * 128 - 1); //Change 128 to change unity value (eg 64) |
Clemo | 0:cb80470434eb | 249 | pitch_pbendlookup = (byte)g; //convert to byte |
Clemo | 0:cb80470434eb | 250 | pitch_lastpbend = MIDI_Get_PitchBend_Level(); //set last pbend value |
Clemo | 0:cb80470434eb | 251 | } |
Clemo | 0:cb80470434eb | 252 | return pitch_pbendlookup; |
Clemo | 0:cb80470434eb | 253 | } |
Clemo | 0:cb80470434eb | 254 |