1#!/usr/bin/python2.5 2# 3# Copyright 2014 Emilie Gillet. 4# 5# Author: Emilie Gillet (emilie.o.gillet@gmail.com) 6# 7# Permission is hereby granted, free of charge, to any person obtaining a copy 8# of this software and associated documentation files (the "Software"), to deal 9# in the Software without restriction, including without limitation the rights 10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11# copies of the Software, and to permit persons to whom the Software is 12# furnished to do so, subject to the following conditions: 13# 14# The above copyright notice and this permission notice shall be included in 15# all copies or substantial portions of the Software. 16# 17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23# THE SOFTWARE. 24# 25# See http://creativecommons.org/licenses/MIT/ for more information. 26# 27# ----------------------------------------------------------------------------- 28# 29# Lookup table definitions. 30 31import numpy 32 33lookup_tables = [] 34int16_lookup_tables = [] 35uint32_lookup_tables = [] 36 37SAMPLE_RATE = 32000.0 38 39 40"""---------------------------------------------------------------------------- 41Sine table 42----------------------------------------------------------------------------""" 43 44WAVETABLE_SIZE = 4096 45t = numpy.arange(0.0, WAVETABLE_SIZE + 1) / WAVETABLE_SIZE 46t[-1] = t[0] 47 48x = numpy.sin(2 * numpy.pi * t) 49lookup_tables += [('sine', x)] 50 51 52 53"""---------------------------------------------------------------------------- 54Coefficients for approximate filter, 32Hz to 16kHz ; Q = 0.5 to 500 55----------------------------------------------------------------------------""" 56 57frequency = 32 * (10 ** (2.7 * numpy.arange(0, 257) / 256)) 58frequency /= SAMPLE_RATE 59 60frequency[frequency >= 0.499] = 0.499 61 62g = numpy.tan(numpy.pi * frequency) 63r = 2.0 64h = 1.0 / (1.0 + r * g + g * g) 65gain = (0.42 / frequency) * (4 ** (frequency * frequency)) 66r = 1 / (0.5 * 10 ** (3.0 * numpy.arange(0, 257) / 256)) 67 68lookup_tables += [ 69 ('approx_svf_gain', gain), 70 ('approx_svf_g', g), 71 ('approx_svf_r', r), 72 ('approx_svf_h', h)] 73 74 75 76"""---------------------------------------------------------------------------- 77Exponentials covering several decades in 256 steps, with safeguard 78----------------------------------------------------------------------------""" 79 80x = numpy.arange(0, 257) / 256.0 81lookup_tables += [('4_decades', 10 ** (4 * x))] 82 83 84 85"""---------------------------------------------------------------------------- 863dB/V table for accent/strength control 87----------------------------------------------------------------------------""" 88 89x = numpy.arange(0, 257) / 256.0 90coarse = 10 ** (1.5 * (x - 0.5)) 91fine = 10 ** (x * numpy.log10(coarse[1] / coarse[0])) 92lookup_tables += [('accent_gain_coarse', coarse)] 93lookup_tables += [('accent_gain_fine', fine)] 94 95 96 97"""---------------------------------------------------------------------------- 98dB brightness table 99----------------------------------------------------------------------------""" 100 101x = numpy.arange(0, 513) / 512.0 102x[0] = x[1] 103x[-1] = x[-2] 104brightness = (9 + numpy.log2(x)) / 9 105int16_lookup_tables += [('db_led_brightness', brightness * 256.0)] 106 107 108 109"""---------------------------------------------------------------------------- 110Stiffness table. 111----------------------------------------------------------------------------""" 112 113geometry = numpy.arange(0, 257) / 256.0 114stiffness = geometry + 0 115for i, g in enumerate(geometry): 116 if g < 0.25: 117 g = 0.25 - g 118 stiffness[i] = -g * 0.25 119 elif g < 0.3: 120 stiffness[i] = 0.0 121 elif g < 0.9: 122 g -= 0.3 123 g /= 0.6 124 stiffness[i] = 0.01 * 10 ** (g * 2.005) - 0.01 125 else: 126 g -= 0.9 127 g /= 0.1 128 g *= g 129 stiffness[i] = 1.5 - numpy.cos(g * numpy.pi) / 2.0 130 131stiffness[-1] = 2.0 132stiffness[-2] = 2.0 133lookup_tables += [('stiffness', stiffness)] 134 135 136 137"""---------------------------------------------------------------------------- 138Envelope increments and curves. 139----------------------------------------------------------------------------""" 140 141TABLE_SIZE = 256 142t = numpy.arange(0.0, TABLE_SIZE + 2) / TABLE_SIZE 143t[-1] = t[-2] = 1.0 144 145control_rate = SAMPLE_RATE / 16.0 146max_time = 8.0 # seconds 147min_time = 0.0005 148gamma = 0.175 149min_increment = 1.0 / (max_time * control_rate) 150max_increment = 1.0 / (min_time * control_rate) 151a = numpy.power(max_increment, -gamma) 152b = numpy.power(min_increment, -gamma) 153 154lookup_tables.append( 155 ('env_increments', numpy.power(a + (b - a) * t, -1 / gamma)) 156) 157 158env_linear = t 159env_quartic = t ** 3.32 160env_expo = 1.0 - numpy.exp(-4 * t) 161 162lookup_tables.append(('env_linear', env_linear / env_linear.max())) 163lookup_tables.append(('env_expo', env_expo / env_expo.max())) 164lookup_tables.append(('env_quartic', env_quartic / env_quartic.max())) 165 166 167 168"""---------------------------------------------------------------------------- 169MIDI to normalized frequency table. 170----------------------------------------------------------------------------""" 171 172TABLE_SIZE = 256 173 174midi_note = numpy.arange(0, TABLE_SIZE) - 48 175frequency = 440 * 2 ** ((midi_note - 69) / 12.0) 176max_frequency = min(12000, SAMPLE_RATE / 2) 177frequency[frequency >= max_frequency] = max_frequency 178frequency /= SAMPLE_RATE 179 180semitone = 2 ** (numpy.arange(0, TABLE_SIZE) / 256.0 / 12.0) 181 182 183lookup_tables.append(('midi_to_f_high', frequency)) 184lookup_tables.append(('midi_to_increment_high', frequency * (1 << 32))) 185lookup_tables.append(('midi_to_f_low', semitone)) 186 187 188 189"""---------------------------------------------------------------------------- 190Quantizer for FM frequencies. 191----------------------------------------------------------------------------""" 192 193fm_frequency_ratios = [ 0.5, 0.5 * 2 ** (16 / 1200.0), 194 numpy.sqrt(2) / 2, numpy.pi / 4, 1.0, 1.0 * 2 ** (16 / 1200.0), numpy.sqrt(2), 195 numpy.pi / 2, 7.0 / 4, 2, 2 * 2 ** (16 / 1200.0), 9.0 / 4, 11.0 / 4, 196 2 * numpy.sqrt(2), 3, numpy.pi, numpy.sqrt(3) * 2, 4, numpy.sqrt(2) * 3, 197 numpy.pi * 3 / 2, 5, numpy.sqrt(2) * 4, 8] 198 199scale = [] 200for ratio in fm_frequency_ratios: 201 ratio = 12 * numpy.log2(ratio) 202 scale.extend([ratio, ratio, ratio]) 203 204target_size = int(2 ** numpy.ceil(numpy.log2(len(scale)))) 205while len(scale) < target_size: 206 gap = numpy.argmax(numpy.diff(scale)) 207 scale = scale[:gap + 1] + [(scale[gap] + scale[gap + 1]) / 2] + \ 208 scale[gap + 1:] 209 210scale.append(scale[-1]) 211 212lookup_tables.append( 213 ('fm_frequency_quantizer', scale) 214) 215 216 217 218"""---------------------------------------------------------------------------- 219Quantizer for FM frequencies. 220----------------------------------------------------------------------------""" 221 222detune_ratios = [-24, -12, -11.95, -5.0, -0.05, 0.0, 0.05, 7.0, 12.0, 19.0, 24.0] 223 224scale = [] 225for ratio in detune_ratios: 226 scale.extend([ratio, ratio, ratio]) 227 228target_size = int(2 ** numpy.ceil(numpy.log2(len(scale)))) 229while len(scale) < target_size: 230 gap = numpy.argmax(numpy.diff(scale)) 231 scale = scale[:gap + 1] + [(scale[gap] + scale[gap + 1]) / 2] + \ 232 scale[gap + 1:] 233 234scale.append(scale[-1]) 235 236lookup_tables.append( 237 ('detune_quantizer', scale) 238) 239 240 241 242"""---------------------------------------------------------------------------- 243Delay compensation factor for SVF 244----------------------------------------------------------------------------""" 245 246ratio = 2.0 ** (numpy.arange(0, 257) / 12.0) 247svf_shift = 2.0 * numpy.arctan(1.0 / ratio) / (2.0 * numpy.pi) 248lookup_tables += [('svf_shift', svf_shift)]