mirror of
https://github.com/sjlongland/polyphonic-synthesizer.git
synced 2025-09-13 08:42:22 +10:00
Check-in of polyphonic synthesizer
This commit is contained in:
commit
d3e57436f9
340
LICENSE
Normal file
340
LICENSE
Normal file
@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
25
Makefile
Normal file
25
Makefile
Normal file
@ -0,0 +1,25 @@
|
||||
# Compiler definitions
|
||||
CROSS_COMPILE ?= avr-
|
||||
CC = $(CROSS_COMPILE)gcc
|
||||
OBJCOPY = $(CROSS_COMPILE)objcopy
|
||||
MCU ?= attiny85
|
||||
CFLAGS ?= -mmcu=$(MCU) -Os
|
||||
CPPFLAGS ?= -DF_CPU=8000000 -D_POLY_CFG=\"poly_cfg.h\"
|
||||
LDFLAGS ?= -mmcu=$(MCU) -Os -Wl,--as-needed
|
||||
|
||||
all: synth.hex
|
||||
|
||||
%.hex: %.elf
|
||||
$(OBJCOPY) -j .text -j .data -O ihex $< $@
|
||||
|
||||
synth.elf: main.o poly.o
|
||||
$(CC) -o $@ $(LDFLAGS) $^
|
||||
|
||||
poly.o: poly.h
|
||||
main.o: poly.h
|
||||
|
||||
%.E: %.c
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -E $^
|
||||
|
||||
clean:
|
||||
-rm -f *.o *.hex *.elf
|
11
Makefile.pc
Normal file
11
Makefile.pc
Normal file
@ -0,0 +1,11 @@
|
||||
# vim: set filetype=make:
|
||||
# Makefile for building synthesizer test application on PC
|
||||
# Requires libao
|
||||
|
||||
LIBS=-lao
|
||||
|
||||
pctest: poly.pc.o pctest.pc.o
|
||||
$(CC) $(LIBS) $(LDFLAGS) -o $@ $^
|
||||
|
||||
%.pc.o: %.c
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
78
README.md
Normal file
78
README.md
Normal file
@ -0,0 +1,78 @@
|
||||
Polyphonic Synthesizer
|
||||
======================
|
||||
|
||||
This project is intended to be a polyphonic synthesizer for use in
|
||||
embedded microcontrollers. It features multi-voice synthesis for up to 16
|
||||
channels, with channels outputting either a fixed DC offset, sinusoidal
|
||||
output or whitenoise.
|
||||
|
||||
The phase or amplitude of one channel may be modulated by the output of
|
||||
another channel, allowing for various effects.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
There are two ways to configure this library:
|
||||
|
||||
Using C preprocessor constants
|
||||
------------------------------
|
||||
|
||||
The following C preprocessor definitions should be defined in your project
|
||||
`Makefile`.
|
||||
|
||||
* `_POLY_NUM_CHANNELS`: The number of polyphonic channels (voices) that
|
||||
you wish to instantiate. Each channel occupies 16 bytes.
|
||||
* `_POLY_FREQ`: The output sample rate for the polyphonic synthesizer in
|
||||
Hz.
|
||||
|
||||
Using linker symbols
|
||||
--------------------
|
||||
|
||||
Alternatively, these things can be decided at run-time. Your application
|
||||
should export the following symbols:
|
||||
|
||||
* `const uint8_t poly_num_channels`: The number of polyphonic channels
|
||||
currently configured.
|
||||
* `const uint16_t poly_freq`: The output sample rate for the polyphonic
|
||||
synthesizer in Hz.
|
||||
* `const uint16_t poly_freq_max`: The maximum output frequency for the
|
||||
polyphonic synthesizer. This should be set to `poly_freq/2` (the
|
||||
nyquist frequency).
|
||||
* `struct poly_voice_t poly_voice[]`: The array of voice channels.
|
||||
|
||||
You may declare functions using these symbols, or you may use linker
|
||||
aliasing to expose variables/structures with alternate names.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Global structures
|
||||
-----------------
|
||||
|
||||
A global counter, `poly_remain`, counts down the number of audio samples
|
||||
remaining before the next set of events are due to be loaded. When this
|
||||
variable reaches 0, you should start calling `poly_load` with new data or
|
||||
call `poly_reset` to stop playback.
|
||||
|
||||
Functions
|
||||
---------
|
||||
|
||||
`poly_reset` clears the state of the polyphonic synthesizer, cancelling
|
||||
any in-progress playback. This should be done as part of your program
|
||||
initialisation.
|
||||
|
||||
`poly_load` is used to load in the next event. Events are covered below.
|
||||
|
||||
`poly_next` returns the next audio sample, or `0` if there is no more
|
||||
audio left to be played.
|
||||
|
||||
Events
|
||||
======
|
||||
|
||||
The structure of the event system is loosely based on ideas from the MIDI
|
||||
standard. Essentially, an "event" is a parameter change for a given
|
||||
channel.
|
||||
|
||||
Each event is represented by a 4-byte data structure, a `struct
|
||||
poly_evt_t` which has two fields, `flags` and `value`. `poly.h` covers
|
||||
each of the events. See the comments up the top of that file for detail.
|
208
fifo.h
Normal file
208
fifo.h
Normal file
@ -0,0 +1,208 @@
|
||||
#ifndef _UTIL_FIFO_H
|
||||
#define _UTIL_FIFO_H
|
||||
|
||||
/*!
|
||||
* Simple ring FIFO buffer.
|
||||
*
|
||||
* 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 (see COPYING); if not, write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*!
|
||||
* Empty event. Indicates that the buffer is now empty and the next read
|
||||
* will generate an underrun.
|
||||
*/
|
||||
#define FIFO_EVT_EMPTY (1 << 0)
|
||||
|
||||
/*!
|
||||
* Underrun event flag, indicates that the consumer tried to read when the
|
||||
* buffer was empty.
|
||||
*/
|
||||
#define FIFO_EVT_UNDERRUN (1 << 1)
|
||||
|
||||
/*!
|
||||
* Data arrived event. Indicates that new data has arrived.
|
||||
*/
|
||||
#define FIFO_EVT_NEW (1 << 2)
|
||||
|
||||
/*!
|
||||
* Buffer full event. Indicates that the buffer is now full and the next
|
||||
* write will generate an overrun.
|
||||
*/
|
||||
#define FIFO_EVT_FULL (1 << 3)
|
||||
|
||||
/*!
|
||||
* Overrun event flag, indicates that the producer tried to write when the
|
||||
* buffer was full.
|
||||
*/
|
||||
#define FIFO_EVT_OVERRUN (1 << 4)
|
||||
|
||||
/*!
|
||||
* FIFO Buffer interface.
|
||||
*/
|
||||
struct fifo_t {
|
||||
/*! FIFO producer event handler */
|
||||
void (*producer_evth)(struct fifo_t* const fifo, uint8_t events);
|
||||
|
||||
/*! FIFO consumer event handler */
|
||||
void (*consumer_evth)(struct fifo_t* const fifo, uint8_t events);
|
||||
|
||||
volatile uint8_t* buffer; /*!< Buffer storage location */
|
||||
uint8_t total_sz; /*!< Buffer total size */
|
||||
volatile uint8_t stored_sz; /*!< Buffer usage size */
|
||||
volatile uint8_t read_ptr; /*!< Read pointer location */
|
||||
volatile uint8_t write_ptr; /*!< Write pointer location */
|
||||
|
||||
uint8_t producer_evtm; /*!< Producer event mask */
|
||||
uint8_t consumer_evtm; /*!< Consumer event mask */
|
||||
|
||||
void* producer_data; /*!< Producer data pointer */
|
||||
void* consumer_data; /*!< Consumer data pointer */
|
||||
};
|
||||
|
||||
/*!
|
||||
* Execute one or more FIFO events.
|
||||
*/
|
||||
static void fifo_exec(struct fifo_t* const fifo, uint8_t events) {
|
||||
if (fifo->producer_evth && (fifo->producer_evtm & events))
|
||||
fifo->producer_evth(fifo, events);
|
||||
if (fifo->consumer_evth && (fifo->consumer_evtm & events))
|
||||
fifo->consumer_evth(fifo, events);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Empty the buffer.
|
||||
*/
|
||||
static void fifo_empty(struct fifo_t* const fifo) {
|
||||
fifo->stored_sz = 0;
|
||||
fifo->read_ptr = 0;
|
||||
fifo->write_ptr = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Initialise the buffer
|
||||
*/
|
||||
static void fifo_init(struct fifo_t* const fifo,
|
||||
volatile uint8_t* buffer, uint8_t sz) {
|
||||
fifo_empty(fifo);
|
||||
fifo->buffer = buffer;
|
||||
fifo->total_sz = sz;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Read a byte from the buffer. Returns the byte read, or -1 if no
|
||||
* data is available.
|
||||
*/
|
||||
static int16_t fifo_read_one(struct fifo_t* const fifo) {
|
||||
if (!fifo->stored_sz) {
|
||||
fifo_exec(fifo, FIFO_EVT_UNDERRUN);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t byte = fifo->buffer[fifo->read_ptr];
|
||||
fifo->stored_sz--;
|
||||
fifo->read_ptr = (fifo->read_ptr + 1) % fifo->total_sz;
|
||||
if (!fifo->stored_sz)
|
||||
fifo_exec(fifo, FIFO_EVT_EMPTY);
|
||||
return byte;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Read a byte from the buffer without consuming it.
|
||||
* Returns the byte read, or -1 if no data is available.
|
||||
*/
|
||||
static int16_t fifo_peek_one(struct fifo_t* const fifo) {
|
||||
if (!fifo->stored_sz)
|
||||
return -1;
|
||||
|
||||
return fifo->buffer[fifo->read_ptr];
|
||||
}
|
||||
|
||||
/*!
|
||||
* Write a byte to the buffer. Returns 1 on success,
|
||||
* 0 if no space available.
|
||||
*/
|
||||
static uint8_t fifo_write_one(struct fifo_t* const fifo, uint8_t byte) {
|
||||
if (fifo->stored_sz >= fifo->total_sz) {
|
||||
fifo_exec(fifo, FIFO_EVT_OVERRUN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fifo->buffer[fifo->write_ptr] = byte;
|
||||
fifo->stored_sz++;
|
||||
fifo->write_ptr = (fifo->write_ptr + 1) % fifo->total_sz;
|
||||
|
||||
if (fifo->stored_sz)
|
||||
fifo_exec(fifo, FIFO_EVT_NEW);
|
||||
|
||||
if (fifo->stored_sz == fifo->total_sz)
|
||||
fifo_exec(fifo, FIFO_EVT_FULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Read bytes from the buffer
|
||||
*/
|
||||
static uint8_t fifo_read(struct fifo_t* const fifo,
|
||||
uint8_t* buffer, uint8_t sz) {
|
||||
uint8_t count = 0;
|
||||
int16_t byte = fifo_read_one(fifo);
|
||||
while(sz && (byte >= 0)) {
|
||||
*buffer = byte;
|
||||
sz--;
|
||||
buffer++;
|
||||
count++;
|
||||
byte = fifo_read_one(fifo);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Read bytes from the buffer without consuming them.
|
||||
*/
|
||||
static uint8_t fifo_peek(struct fifo_t* const fifo,
|
||||
uint8_t* buffer, uint8_t sz) {
|
||||
uint8_t count = 0;
|
||||
uint8_t ptr = fifo->read_ptr;
|
||||
if (sz > fifo->stored_sz)
|
||||
sz = fifo->stored_sz;
|
||||
|
||||
while(sz) {
|
||||
*buffer = fifo->buffer[ptr];
|
||||
sz--;
|
||||
buffer++;
|
||||
count++;
|
||||
ptr = (ptr + 1) % fifo->total_sz;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Write bytes to the buffer
|
||||
*/
|
||||
static uint8_t fifo_write(struct fifo_t* const fifo,
|
||||
const uint8_t* buffer, uint8_t sz) {
|
||||
uint8_t count = 0;
|
||||
while(sz && fifo_write_one(fifo, *buffer)) {
|
||||
buffer++;
|
||||
sz--;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#endif
|
106
main.c
Normal file
106
main.c
Normal file
@ -0,0 +1,106 @@
|
||||
/*!
|
||||
* Polyphonic synthesizer for microcontrollers: Atmel ATTiny85 port.
|
||||
* (C) 2016 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
|
||||
*/
|
||||
|
||||
#include "poly.h"
|
||||
#include "fifo.h"
|
||||
#include <avr/io.h>
|
||||
#include <util/delay.h>
|
||||
#include <avr/interrupt.h>
|
||||
|
||||
#define SAMPLE_LEN 16
|
||||
static volatile uint8_t sample_buffer[SAMPLE_LEN];
|
||||
static struct fifo_t sample_fifo;
|
||||
|
||||
int main(void) {
|
||||
struct poly_evt_t poly_evt;
|
||||
|
||||
/* Turn on all except ADC */
|
||||
PRR = (1 << PRADC);
|
||||
|
||||
/* Start up PLL */
|
||||
PLLCSR = (1 << PLLE);
|
||||
while (!(PLLCSR & (1<<PLOCK)));
|
||||
PLLCSR |= (1<<PCKE);
|
||||
|
||||
fifo_init(&sample_fifo, sample_buffer, SAMPLE_LEN);
|
||||
|
||||
/* Reset the synthesizer */
|
||||
poly_reset();
|
||||
|
||||
/* Enable output on PB4 (PWM out) */
|
||||
DDRB |= (1 << 4) | (1 << 3);
|
||||
PORTB |= (1 << 4) | (1 << 3);
|
||||
|
||||
/* Timer 1 configuration for PWM */
|
||||
OCR1B = 128; /* Initial PWM value */
|
||||
OCR1C = 255; /* Maximum PWM value */
|
||||
TCCR1 = (1 << CS10); /* No prescaling, max speed */
|
||||
GTCCR = (1 << PWM1B) /* Enable PWM */
|
||||
| (2 << COM1B0); /* Clear output bit on match */
|
||||
|
||||
/* Timer 0 configuration for sample rate interrupt */
|
||||
TCCR0A = (2 << WGM00); /* CTC mode */
|
||||
TCCR0B = (1 << CS00); /* No prescaling */
|
||||
OCR0A = F_CPU / _POLY_FREQ; /* Sample rate */
|
||||
TIMSK |= (1 << OCIE0A); /* Enable interrupts */
|
||||
|
||||
/* Configure the synthesizer */
|
||||
poly_evt.flags = POLY_EVT_TYPE_ENABLE;
|
||||
poly_evt.value = 1;
|
||||
poly_load(&poly_evt);
|
||||
|
||||
poly_evt.flags = POLY_EVT_TYPE_IFREQ;
|
||||
poly_evt.value = 1000;
|
||||
poly_load(&poly_evt);
|
||||
|
||||
poly_evt.flags = POLY_EVT_TYPE_IAMP;
|
||||
poly_evt.value = 255;
|
||||
poly_load(&poly_evt);
|
||||
|
||||
poly_evt.flags = POLY_EVT_TYPE_ASCALE;
|
||||
poly_evt.value = 8;
|
||||
poly_load(&poly_evt);
|
||||
|
||||
sei();
|
||||
while(1) {
|
||||
poly_evt.flags = POLY_EVT_TYPE_TIME;
|
||||
poly_evt.value = 64000;
|
||||
poly_load(&poly_evt);
|
||||
|
||||
while (poly_remain) {
|
||||
while (sample_fifo.stored_sz < SAMPLE_LEN) {
|
||||
int16_t s = poly_next();
|
||||
fifo_write_one(&sample_fifo,
|
||||
128 + (s >> 9));
|
||||
|
||||
}
|
||||
PORTB ^= (1 << 3);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ISR(TIM0_COMPA_vect) {
|
||||
uint8_t sample = fifo_read_one(&sample_fifo);
|
||||
if (sample >= 0)
|
||||
OCR1B = sample;
|
||||
else
|
||||
OCR1B = 128;
|
||||
}
|
165
pctest.c
Normal file
165
pctest.c
Normal file
@ -0,0 +1,165 @@
|
||||
#include "poly.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ao/ao.h>
|
||||
|
||||
const uint16_t poly_freq = 32000;
|
||||
const uint16_t poly_freq_max = 16000;
|
||||
const uint8_t poly_num_channels = 8;
|
||||
struct poly_voice_t poly_voice[8];
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
struct poly_evt_t event;
|
||||
int voice = 0;
|
||||
int16_t samples[8192];
|
||||
uint16_t samples_sz = 0;
|
||||
ao_device* device;
|
||||
ao_sample_format format;
|
||||
ao_initialize();
|
||||
poly_reset();
|
||||
FILE* out = fopen("out.raw", "wb");
|
||||
|
||||
{
|
||||
int driver = ao_default_driver_id();
|
||||
memset(&format, 0, sizeof(format));
|
||||
format.bits = 16;
|
||||
format.channels = 1;
|
||||
format.rate = poly_freq;
|
||||
format.byte_format = AO_FMT_NATIVE;
|
||||
device = ao_open_live(driver, &format, NULL);
|
||||
if (!device) {
|
||||
fprintf(stderr, "Failed to open audio device\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
argc--;
|
||||
argv++;
|
||||
while (argc > 0) {
|
||||
int res = 0;
|
||||
if (!strcmp(argv[0], "end"))
|
||||
break;
|
||||
if (!strcmp(argv[0], "voice")) {
|
||||
voice = atoi(argv[1]);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "mute")) {
|
||||
int mute = atoi(argv[1]);
|
||||
event.flags = POLY_EVT_TYPE_MUTE;
|
||||
event.value = mute;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "en")) {
|
||||
int en = atoi(argv[1]);
|
||||
event.flags = POLY_EVT_TYPE_ENABLE;
|
||||
event.value = en;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "freq")) {
|
||||
int freq = atoi(argv[1]);
|
||||
event.flags = (voice << POLY_CH_BIT)
|
||||
| POLY_EVT_TYPE_IFREQ;
|
||||
event.value = freq;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "dfreq")) {
|
||||
int freq = atoi(argv[1]);
|
||||
event.flags = (voice << POLY_CH_BIT)
|
||||
| POLY_EVT_TYPE_DFREQ;
|
||||
event.value = freq;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "ascale")) {
|
||||
int amp = atoi(argv[1]);
|
||||
event.flags = (voice << POLY_CH_BIT)
|
||||
| POLY_EVT_TYPE_ASCALE;
|
||||
event.value = amp;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "amp")) {
|
||||
int amp = atoi(argv[1]);
|
||||
event.flags = (voice << POLY_CH_BIT)
|
||||
| POLY_EVT_TYPE_IAMP;
|
||||
event.value = amp;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "damp")) {
|
||||
int damp = atoi(argv[1]);
|
||||
event.flags = (voice << POLY_CH_BIT)
|
||||
| POLY_EVT_TYPE_DAMP;
|
||||
event.value = damp;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "pmod")) {
|
||||
int pmod = atoi(argv[1]);
|
||||
event.flags = (voice << POLY_CH_BIT)
|
||||
| POLY_EVT_TYPE_PMOD;
|
||||
event.value = pmod;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "amod")) {
|
||||
int amod = atoi(argv[1]);
|
||||
event.flags = (voice << POLY_CH_BIT)
|
||||
| POLY_EVT_TYPE_AMOD;
|
||||
event.value = amod;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "dscale")) {
|
||||
int dt = atoi(argv[1]);
|
||||
event.flags = (voice << POLY_CH_BIT)
|
||||
| POLY_EVT_TYPE_DSCALE;
|
||||
event.value = dt;
|
||||
res = poly_load(&event);
|
||||
argv++;
|
||||
argc--;
|
||||
} else if (!strcmp(argv[0], "time")) {
|
||||
int time = atoi(argv[1]);
|
||||
argv++;
|
||||
argc--;
|
||||
event.flags = POLY_EVT_TYPE_TIME;
|
||||
event.value = time;
|
||||
res = poly_load(&event);
|
||||
}
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "Failed: %s\n",
|
||||
strerror(-res));
|
||||
break;
|
||||
}
|
||||
argv++;
|
||||
argc--;
|
||||
|
||||
/* Play out any remaining samples */
|
||||
while (poly_remain) {
|
||||
int16_t* sample_ptr = samples;
|
||||
uint16_t samples_remain = 8192;
|
||||
/* Fill the buffer as much as we can */
|
||||
while (poly_remain && samples_remain) {
|
||||
int16_t s = poly_next();
|
||||
//printf("%d: %d\n", samples_sz, s);
|
||||
*sample_ptr = s << 7;
|
||||
sample_ptr++;
|
||||
samples_sz++;
|
||||
samples_remain--;
|
||||
}
|
||||
fwrite(samples, samples_sz, 2, out);
|
||||
ao_play(device, (char*)samples, 2*samples_sz);
|
||||
samples_sz = 0;
|
||||
}
|
||||
}
|
||||
|
||||
poly_reset();
|
||||
fclose(out);
|
||||
|
||||
ao_close(device);
|
||||
ao_shutdown();
|
||||
return 0;
|
||||
}
|
332
poly.c
Normal file
332
poly.c
Normal file
@ -0,0 +1,332 @@
|
||||
/*!
|
||||
* Polyphonic synthesizer for microcontrollers.
|
||||
* (C) 2016 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
|
||||
*/
|
||||
|
||||
#include "poly.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __AVR_ARCH__
|
||||
#include <avr/pgmspace.h>
|
||||
#endif
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include <stdio.h>
|
||||
#define _DPRINTF(s, a...) printf(__FILE__ ": %d " s, __LINE__, a)
|
||||
#else
|
||||
#define _DPRINTF(s, a...)
|
||||
#endif
|
||||
|
||||
#ifdef _POLY_NUM_CHANNELS
|
||||
static struct poly_voice_t poly_voice[_POLY_NUM_CHANNELS];
|
||||
#endif
|
||||
|
||||
#define POLY_SINE_SZ 360
|
||||
static const uint8_t _poly_sine[POLY_SINE_SZ];
|
||||
|
||||
/* Master sample clock */
|
||||
/*static volatile uint16_t _poly_remain __attribute__((nocommon)) = 0;
|
||||
extern const volatile uint16_t
|
||||
__attribute__((alias ("_poly_remain"))) poly_remain;*/
|
||||
volatile uint16_t poly_remain;
|
||||
#define _poly_remain poly_remain
|
||||
|
||||
/* Enabled channels */
|
||||
static uint16_t _poly_enable = 0;
|
||||
/* Muted channels */
|
||||
static uint16_t _poly_mute = 0;
|
||||
|
||||
/*!
|
||||
* Reset the polyphonic synthesizer.
|
||||
*/
|
||||
void poly_reset() {
|
||||
memset(poly_voice, 0,
|
||||
sizeof(struct poly_voice_t)*poly_num_channels);
|
||||
_poly_remain = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Load a sample event into the polyphonic registers.
|
||||
* @param event Polyphonic event to load.
|
||||
*/
|
||||
int poly_load(const struct poly_evt_t* const event) {
|
||||
uint16_t type = (event->flags) & POLY_EVT_TYPE_MASK;
|
||||
switch (type) {
|
||||
case POLY_EVT_TYPE_TIME:
|
||||
_poly_remain = event->value;
|
||||
return 0;
|
||||
case POLY_EVT_TYPE_END:
|
||||
poly_reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Forbid updating of voice states while we are waiting! */
|
||||
if (_poly_remain)
|
||||
return -EINPROGRESS;
|
||||
|
||||
switch (type) {
|
||||
case POLY_EVT_TYPE_ENABLE:
|
||||
_poly_enable = event->value;
|
||||
return 0;
|
||||
case POLY_EVT_TYPE_MUTE:
|
||||
_poly_mute = event->value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct poly_voice_t* const voice = &poly_voice[
|
||||
(event->flags >> POLY_CH_BIT) & 0x0f];
|
||||
switch (type) {
|
||||
case POLY_EVT_TYPE_IFREQ:
|
||||
voice->freq = event->value;
|
||||
voice->time = 0;
|
||||
return 0;
|
||||
case POLY_EVT_TYPE_DFREQ:
|
||||
voice->dfreq = event->value;
|
||||
return 0;
|
||||
case POLY_EVT_TYPE_PMOD:
|
||||
if (event->value == UINT16_MAX)
|
||||
voice->pmod = 0;
|
||||
else
|
||||
voice->pmod = event->value | 0x80;
|
||||
return 0;
|
||||
case POLY_EVT_TYPE_IAMP:
|
||||
voice->amp = event->value;
|
||||
return 0;
|
||||
case POLY_EVT_TYPE_DAMP:
|
||||
voice->damp = event->value;
|
||||
return 0;
|
||||
case POLY_EVT_TYPE_AMOD:
|
||||
if (event->value == UINT16_MAX)
|
||||
voice->amod = 0;
|
||||
else
|
||||
voice->amod = event->value | 0x80;
|
||||
return 0;
|
||||
case POLY_EVT_TYPE_ASCALE:
|
||||
if (event->value > 31)
|
||||
return -ERANGE;
|
||||
voice->ascale = event->value;
|
||||
return 0;
|
||||
case POLY_EVT_TYPE_DSCALE:
|
||||
voice->dscale = event->value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If we get here, then it was a bad event */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Emit the sinusoid at the given fixed-point angle in ¼ degrees.
|
||||
*/
|
||||
static int16_t poly_sine(uint16_t angle) {
|
||||
int16_t amp = 1;
|
||||
angle %= (POLY_SINE_SZ*4);
|
||||
if (angle >= (POLY_SINE_SZ*2)) {
|
||||
amp = -1;
|
||||
angle = (POLY_SINE_SZ*4) - angle - 1;
|
||||
}
|
||||
if (angle >= POLY_SINE_SZ)
|
||||
angle = (POLY_SINE_SZ*2) - angle - 1;
|
||||
|
||||
assert(angle < POLY_SINE_SZ);
|
||||
return amp *
|
||||
#ifdef __AVR_ARCH__
|
||||
pgm_read_byte(&_poly_sine[angle])
|
||||
#else
|
||||
_poly_sine[angle]
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compute the output of a single voice.
|
||||
*/
|
||||
static void poly_compute(struct poly_voice_t* const voice) {
|
||||
int32_t amp = voice->amp;
|
||||
int32_t sample = 0;
|
||||
|
||||
/* Amplitude modulation? */
|
||||
if (voice->amod) {
|
||||
_DPRINTF("amplitude mod: %d + amp(%d)\n",
|
||||
amp, voice->amod & 0x0f);
|
||||
amp += poly_voice[voice->amod & 0x0f].sample;
|
||||
}
|
||||
|
||||
_DPRINTF("amplitude %d\n", amp);
|
||||
if (amp) {
|
||||
/* Frequency modulation? */
|
||||
if (voice->freq) {
|
||||
if (voice->freq < UINT16_MAX) {
|
||||
/*
|
||||
* Time T is N/Fs.
|
||||
* Angle in ¼° is 1440*F*T
|
||||
*/
|
||||
int64_t angle = (4*POLY_SINE_SZ)
|
||||
* voice->freq
|
||||
* (int64_t)voice->time;
|
||||
angle /= poly_freq;
|
||||
if (voice->pmod)
|
||||
angle += poly_voice[voice->pmod
|
||||
& 0x0f].sample;
|
||||
angle %= (4*POLY_SINE_SZ);
|
||||
sample = poly_sine(angle);
|
||||
_DPRINTF("sine %d Hz sample %d "
|
||||
"(angle %ld) = %d\n",
|
||||
voice->freq, voice->time,
|
||||
angle, sample);
|
||||
} else {
|
||||
sample = (rand() / (RAND_MAX/512)) - 256;
|
||||
_DPRINTF("noise %d @ %d\n", sample, amp);
|
||||
}
|
||||
sample *= amp;
|
||||
_DPRINTF("amplitude * sample = %d\n",
|
||||
sample * amp);
|
||||
} else {
|
||||
/* DC */
|
||||
_DPRINTF("DC = %d\n", amp);
|
||||
sample = amp;
|
||||
}
|
||||
sample >>= voice->ascale;
|
||||
_DPRINTF("scale %d = %d\n", voice->ascale, sample);
|
||||
} else {
|
||||
/* No signal */
|
||||
sample = 0;
|
||||
}
|
||||
|
||||
if (voice->dscale && (!(voice->time % voice->dscale))) {
|
||||
/* Delta frequency adjustment */
|
||||
if (voice->dfreq) {
|
||||
int32_t freq = voice->freq;
|
||||
freq += voice->dfreq;
|
||||
if (freq < 0)
|
||||
voice->freq = 0;
|
||||
else if (freq > poly_freq_max)
|
||||
voice->freq = poly_freq_max;
|
||||
else
|
||||
voice->freq = freq;
|
||||
}
|
||||
|
||||
/* Delta amplitude adjustment */
|
||||
if (voice->damp) {
|
||||
amp = (int32_t)voice->amp + (int32_t)voice->damp;
|
||||
if (amp < 0) {
|
||||
voice->amp = 0;
|
||||
voice->damp = 0;
|
||||
} else if (amp > UINT8_MAX) {
|
||||
voice->amp = UINT8_MAX;
|
||||
voice->damp = 0;
|
||||
} else
|
||||
voice->amp = amp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Clipping */
|
||||
if (sample > INT16_MAX)
|
||||
sample = INT16_MAX;
|
||||
else if (sample < INT16_MIN)
|
||||
sample = INT16_MIN;
|
||||
|
||||
/* Update sample */
|
||||
voice->sample = sample;
|
||||
|
||||
/* Time step update */
|
||||
voice->time++;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Retrieve the next output sample from the polyphonic synthesizer.
|
||||
*/
|
||||
int16_t poly_next() {
|
||||
/* Do not return samples unless we're in the waiting state. */
|
||||
if (!_poly_remain)
|
||||
return 0;
|
||||
|
||||
/* Compute all the voices, tally up the samples */
|
||||
uint8_t vid;
|
||||
int16_t sample = 0;
|
||||
uint16_t mask = 1;
|
||||
for (vid = 0; vid < poly_num_channels; vid++) {
|
||||
if (_poly_enable & mask) {
|
||||
_DPRINTF("compute %d\n", vid);
|
||||
poly_compute(&poly_voice[vid]);
|
||||
} else {
|
||||
_DPRINTF("skip compute %d\n", vid);
|
||||
}
|
||||
if (!(_poly_mute & mask)) {
|
||||
sample += poly_voice[vid].sample;
|
||||
} else {
|
||||
_DPRINTF("muted %d\n", vid);
|
||||
}
|
||||
mask <<= 1;
|
||||
}
|
||||
|
||||
/* Decrement our global sample counter */
|
||||
_poly_remain--;
|
||||
return sample;
|
||||
}
|
||||
|
||||
static const uint8_t _poly_sine[POLY_SINE_SZ]
|
||||
#ifdef __AVR_ARCH__
|
||||
PROGMEM
|
||||
#endif
|
||||
= {
|
||||
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A,
|
||||
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x14, 0x15,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1F, 0x20,
|
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x2A, 0x2B,
|
||||
0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40,
|
||||
0x41, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B,
|
||||
0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x53, 0x54, 0x55, 0x56,
|
||||
0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
||||
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
|
||||
0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E,
|
||||
0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
|
||||
0x89, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91,
|
||||
0x92, 0x93, 0x94, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
|
||||
0x9B, 0x9C, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3,
|
||||
0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA8, 0xA9, 0xAA, 0xAB,
|
||||
0xAC, 0xAD, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB1, 0xB2, 0xB3,
|
||||
0xB4, 0xB5, 0xB5, 0xB6, 0xB7, 0xB8, 0xB8, 0xB9, 0xBA, 0xBB,
|
||||
0xBC, 0xBC, 0xBD, 0xBE, 0xBE, 0xBF, 0xC0, 0xC1, 0xC1, 0xC2,
|
||||
0xC3, 0xC4, 0xC4, 0xC5, 0xC6, 0xC6, 0xC7, 0xC8, 0xC8, 0xC9,
|
||||
0xCA, 0xCA, 0xCB, 0xCC, 0xCC, 0xCD, 0xCE, 0xCE, 0xCF, 0xD0,
|
||||
0xD0, 0xD1, 0xD2, 0xD2, 0xD3, 0xD4, 0xD4, 0xD5, 0xD5, 0xD6,
|
||||
0xD7, 0xD7, 0xD8, 0xD8, 0xD9, 0xDA, 0xDA, 0xDB, 0xDB, 0xDC,
|
||||
0xDC, 0xDD, 0xDD, 0xDE, 0xDF, 0xDF, 0xE0, 0xE0, 0xE1, 0xE1,
|
||||
0xE2, 0xE2, 0xE3, 0xE3, 0xE4, 0xE4, 0xE5, 0xE5, 0xE6, 0xE6,
|
||||
0xE7, 0xE7, 0xE8, 0xE8, 0xE8, 0xE9, 0xE9, 0xEA, 0xEA, 0xEB,
|
||||
0xEB, 0xEC, 0xEC, 0xEC, 0xED, 0xED, 0xEE, 0xEE, 0xEE, 0xEF,
|
||||
0xEF, 0xEF, 0xF0, 0xF0, 0xF1, 0xF1, 0xF1, 0xF2, 0xF2, 0xF2,
|
||||
0xF3, 0xF3, 0xF3, 0xF4, 0xF4, 0xF4, 0xF5, 0xF5, 0xF5, 0xF6,
|
||||
0xF6, 0xF6, 0xF6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF8, 0xF8, 0xF8,
|
||||
0xF8, 0xF9, 0xF9, 0xF9, 0xF9, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA,
|
||||
0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC,
|
||||
0xFC, 0xFC, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD,
|
||||
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
|
||||
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* vim: set sw=8 ts=8 noet si tw=72
|
||||
*/
|
252
poly.h
Normal file
252
poly.h
Normal file
@ -0,0 +1,252 @@
|
||||
#ifndef _POLY_H
|
||||
#define _POLY_H
|
||||
|
||||
/*!
|
||||
* Polyphonic synthesizer for microcontrollers.
|
||||
* (C) 2016 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
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
/*
|
||||
* _POLY_CFG_H is a definition that can be given when compiling poly.c
|
||||
* and firmware code to define a header file that contains definitions for
|
||||
* the Polyphonic synthesizer.
|
||||
*/
|
||||
#ifdef _POLY_CFG
|
||||
#include _POLY_CFG
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* Polyphonic event. This is a base struct for describing what happens
|
||||
* within a voice channel. An array of these forms a musical piece or
|
||||
* sound effect. Each event can take the form of a chnage to a voice's
|
||||
* parameters, or timing events.
|
||||
*/
|
||||
struct poly_evt_t {
|
||||
uint16_t flags; /*!< Event flags */
|
||||
uint16_t value; /*!< New register value */
|
||||
};
|
||||
|
||||
/* Event flags */
|
||||
|
||||
/*!
|
||||
* Bits 15-12 represent the event type.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_BIT (12)
|
||||
|
||||
/*!
|
||||
* Bit mask for event type.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_MASK (0x0f << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* END event. This terminates the musical piece and causes a reset of
|
||||
* the synthesizer state. Event is valid at any time.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_END (0x00 << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* TIME event. This indicates the synthesizer should emit samples with the
|
||||
* currently defined parameters for the number of samples given in the
|
||||
* value field. Event is valid at any time.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_TIME (0x01 << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* ENABLE event. This turns on and off computation of the named channels.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_ENABLE (0x02 << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* MUTE event. This turns on and off inclusion of a channel in the output.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_MUTE (0x03 << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* IFREQ change event. This indicates the immediate frequency for a voice
|
||||
* channel is to change to the value given in Hz, or, if the frequency is
|
||||
* zero, the channel is to emit a DC or linearly varying signal.
|
||||
*
|
||||
* Channel number is given in bits 12-8 of the flags register.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_IFREQ (0x04 << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* DFREQ change event. This indicates the frequency step is to change to
|
||||
* the given value in Hz. The frequency of the channel will step by this
|
||||
* amount every N samples, where N is set by DSCALE.
|
||||
*
|
||||
* Channel number is given in bits 12-8 of the flags register.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_DFREQ (0x05 << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* PMOD change event. Change phase modulation configuration.
|
||||
*
|
||||
* If value is UINT16_MAX: Disable phase modulation
|
||||
* Otherwise, modulate the phase using the channel number given.
|
||||
*
|
||||
* Channel number is given in bits 12-8 of the flags register.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_PMOD (0x06 << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* IAMP change event. This indicates the immediate amplitude of the
|
||||
* channel is to be set to the value given.
|
||||
*
|
||||
* Channel number is given in bits 12-8 of the flags register.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_IAMP (0x08 << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* DAMP change event. This indicates the amplitude step is to change to
|
||||
* the given value. The amplitude will change by this amount every N
|
||||
* samples, where N is set by DSCALE.
|
||||
*
|
||||
* Channel number is given in bits 12-8 of the flags register.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_DAMP (0x09 << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* AMOD change event. Change amplitude modulation configuration.
|
||||
*
|
||||
* If value is UINT16_MAX: Disable amplitude modulation
|
||||
* Otherwise, modulate the amplitude using the channel number given.
|
||||
*
|
||||
* Channel number is given in bits 12-8 of the flags register.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_AMOD (0x0a << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* ASCALE change event. Amplitudes are to be left-shifted by this number
|
||||
* of bits after multiplication with a sample.
|
||||
*
|
||||
* Channel number is given in bits 12-8 of the flags register.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_ASCALE (0x0b << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* DSCALE change event. Every N samples (given here), the amplitude and
|
||||
* frequency of the channel will be adjusted.
|
||||
*
|
||||
* Channel number is given in bits 12-8 of the flags register.
|
||||
*/
|
||||
#define POLY_EVT_TYPE_DSCALE (0x0f << POLY_EVT_TYPE_BIT)
|
||||
|
||||
/*!
|
||||
* Position of channel number field.
|
||||
*/
|
||||
#define POLY_CH_BIT (8)
|
||||
|
||||
/*!
|
||||
* Mask for channel number field.
|
||||
*/
|
||||
#define POLY_CH_MASK (0x0f << POLY_VOICE_CH_BIT)
|
||||
|
||||
/*!
|
||||
* Voice state machine. A "voice" is simply a sinusoidal channel. It
|
||||
* may be modulated by a static linear function, or by taking the output
|
||||
* from another channel and summing that.
|
||||
*
|
||||
* Each voice has its own sample timing counter which starts at zero and
|
||||
* counts upwards.
|
||||
*/
|
||||
struct poly_voice_t {
|
||||
int16_t sample; /*!< Sample last computed */
|
||||
uint16_t time; /*!< Time (samples) for voice */
|
||||
uint16_t freq; /*!< Current frequency */
|
||||
int16_t dfreq; /*!< Delta frequency */
|
||||
uint16_t dscale; /*!< Delta time scale */
|
||||
uint8_t amp; /*!< Current amplitude */
|
||||
int8_t damp; /*!< Delta amplitude */
|
||||
uint8_t ascale; /*!< Amplitude scale */
|
||||
uint8_t pmod; /*!< Phase modulation channel */
|
||||
uint8_t amod; /*!< Amplitude modulation channel */
|
||||
uint8_t flags; /*!< Flags register */
|
||||
};
|
||||
|
||||
#ifndef _POLY_NUM_CHANNELS
|
||||
/*!
|
||||
* Number of voice channels: this needs to be declared in the application.
|
||||
* Overridden by defining _POLY_NUM_CHANNELS.
|
||||
*/
|
||||
extern const uint8_t __attribute__((weak)) poly_num_channels;
|
||||
#else
|
||||
#define poly_num_channels _POLY_NUM_CHANNELS
|
||||
#endif
|
||||
|
||||
#ifndef _POLY_FREQ
|
||||
/*!
|
||||
* Sample rate for the synthesizer: this needs to be declared in the
|
||||
* application.
|
||||
*/
|
||||
extern const uint16_t __attribute__((weak)) poly_freq;
|
||||
#else
|
||||
#define poly_freq _POLY_FREQ
|
||||
#endif
|
||||
|
||||
#ifndef _POLY_FREQ
|
||||
/*!
|
||||
* Maximum frequency for the synthesizer: this needs to be declared in the
|
||||
* application. This should be set at the nyquist frequency.
|
||||
* (poly_freq/2)
|
||||
*/
|
||||
extern const uint16_t __attribute__((weak)) poly_freq_max;
|
||||
#else
|
||||
#define poly_freq_max (poly_freq/2)
|
||||
#endif
|
||||
|
||||
#ifndef _POLY_NUM_CHANNELS
|
||||
/*!
|
||||
* Voice channel array: this needs to be declared in the application.
|
||||
*/
|
||||
extern struct __attribute__((weak)) poly_voice_t poly_voice[];
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* Number of samples remaining before the next set of events.
|
||||
*/
|
||||
extern volatile uint16_t poly_remain;
|
||||
|
||||
/*!
|
||||
* Reset the polyphonic synthesizer.
|
||||
*/
|
||||
void poly_reset();
|
||||
|
||||
/*!
|
||||
* Load a sample event into the polyphonic registers.
|
||||
* @param event Polyphonic event to load.
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL Bad event
|
||||
* @retval -ERANGE Bad value
|
||||
* @retval -EINPROGRESS Waiting for timing event
|
||||
*/
|
||||
int poly_load(const struct poly_evt_t* event);
|
||||
|
||||
/*!
|
||||
* Retrieve the next output sample from the polyphonic synthesizer.
|
||||
*/
|
||||
int16_t poly_next();
|
||||
|
||||
/*
|
||||
* vim: set sw=8 ts=8 noet si tw=72
|
||||
*/
|
||||
|
||||
#endif
|
27
poly_cfg.h
Normal file
27
poly_cfg.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef _POLY_CFG_H
|
||||
#define _POLY_CFG_H
|
||||
|
||||
/*!
|
||||
* Polyphonic synthesizer for microcontrollers.
|
||||
* (C) 2016 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
|
||||
*/
|
||||
|
||||
#define _POLY_NUM_CHANNELS 16
|
||||
#define _POLY_FREQ 32000
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user