From 44e2d937d6d2ea7368172bf269ed373f138f5c12 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Tue, 25 Apr 2017 17:25:15 +1000 Subject: [PATCH] gensine: Add in sine wave generator. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I left this out because I thought the idea of modulus and division on a MCU that lacks a hardware multiplier would be too much for it… and indeed it was too much with my other project. Thinking about it this afternoon, I had an idea. If I have 2^N samples, then the modulus can be optimised to: ``` mod = sample & ((2**n)-1) ``` and the segment can be figured out by: ``` segment = sample >> n ``` The segment is two bits. A function that returns the scaled sine value for a given scaled angle can be given as: ``` int8_t fp_sine(uint8_t angle) { uint8_t segment = (angle >> POLY_SINE_SZ_BITS) & 3; uint8_t offset = angle & ((1 << POLY_SINE_SZ_BITS)-1); switch (segment) { case 0: return _poly_sine[offset]; case 1: return _poly_sine[ POLY_SINE_SZ - offset]; case 2: return -_poly_sine[offset]; case 3: return -_poly_sine[ POLY_SINE_SZ - offset]; } } ``` … or something like that. If `POLY_SINE_SZ_BITS=6`, then `angle=255` represents 360°. --- .gitignore | 1 + Makefile | 5 +++ gensine.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 gensine.py diff --git a/.gitignore b/.gitignore index 93964d4..7e356c7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ *.o *.elf *.hex +sine.c obj bin diff --git a/Makefile b/Makefile index 2859b2e..52633cf 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,11 @@ $(OBJDIR)/%.o: $(PORTDIR)/%.c $(CC) -MM $(CPPFLAGS) $(INCLUDES) $< \ | sed -e '/^[^ ]\+:/ s:^:$(OBJDIR)/:g' > $@.dep +$(SRCDIR)/sine.c: $(SRCDIR)/gensine.py + python $^ --amplitude 127 --num-samples-bits \ + --num-samples 6 --data-type int8_t \ + > $@ + clean: -rm -fr $(OBJDIR) $(BINDIR) diff --git a/gensine.py b/gensine.py new file mode 100644 index 0000000..c1d6950 --- /dev/null +++ b/gensine.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +""" +Polyphonic synthesizer for microcontrollers: Sine look-up table generator +(C) 2017 Stuart Longland + +This program 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 2 of the License, or +(at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, +MA 02110-1301 USA +""" + +import math +import textwrap +import argparse + +# Arguments +parser = argparse.ArgumentParser( + description='Generate a sine wave look-up table in C' +) +parser.add_argument('--num-samples-bits', + help='--num-samples is in bits, so # samples = 2^n, allowing a '\ + 'bitwise AND to be used for modulus operations and bit '\ + 'shifting for segment decoding.', + action='store_const', default=False, const=True) +parser.add_argument('--num-samples', + help='Number of samples to generate', + type=int, default=10) +parser.add_argument('--amplitude', + help='Amplitude of sine to generate', + type=int, default=255) +parser.add_argument('--data-type', + help='Data type of sine samples', + type=str, default='uint8_t') +args = parser.parse_args() + +# Number of samples +if args.num_samples_bits: + num_samples = 2 ** args.num_samples +else: + num_samples = args.num_samples + +# Step +step_angle = math.pi / (2*num_samples) + +# Samples +samples = [int(args.amplitude*math.sin(sample * step_angle)) + for sample in range(0, num_samples)] + +# Output C file +print ("""#ifndef _GEN_SINE_C +#define _GEN_SINE_C + +/* Generated sine wave */ +#include +#define POLY_SINE_SZ (%(N_SAMPLES)d) +#define POLY_SINE_SZ_BITS (%(N_BITS)d) +static const %(DATA_TYPE)s _poly_sine[POLY_SINE_SZ] +#ifdef __AVR_ARCH__ +PROGMEM +#endif += { +%(SAMPLES)s +}; +#endif""" % { + 'N_SAMPLES': num_samples, + 'N_BITS': args.num_samples if args.num_samples_bits else 0, + 'DATA_TYPE': args.data_type, + 'SAMPLES': '\n'.join( + textwrap.TextWrapper( + width=78, + initial_indent=' ', + subsequent_indent=' ' + ).wrap( + ', '.join([ + ('0x%02x' % sample) for sample in samples + ]) + ) + ) +})