├── .gitignore ├── LICENSE ├── docs ├── Makefile ├── building.html ├── building.md ├── circuit.html ├── circuit.md ├── images │ ├── low_pass.png │ ├── low_pass.sch │ ├── super_basic.png │ └── super_basic.sch ├── song_setup.html └── song_setup.md ├── readme.md ├── test_songs ├── demo1.xrns ├── env_test.xrns ├── faxanadu.xrns ├── instruments │ ├── Square 0x80.xrni │ ├── Square 0xbf.xrni │ ├── Square 0xdd.xrni │ ├── nesTri.xrni │ └── noise.xrni ├── noise.xrns ├── pitch.xrns └── zelda.xrns ├── tinytune ├── settings.h ├── tinytune.c └── tinytune.h ├── tinytune_test ├── Makefile ├── tinytune_test.atsln ├── tinytune_test.c └── tinytune_test.cproj ├── tinytune_test_atmega8 ├── Makefile ├── tinytune_test.atsln ├── tinytune_test.c └── tinytune_test.cproj └── utils └── xrns2tt.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | 11 | # Shared objects (inc. Windows DLLs) 12 | *.dll 13 | *.so 14 | *.so.* 15 | *.dylib 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Blake Livingston (blake.a.livingston@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | 2 | MARKDOWNS = $(wildcard *.md) 3 | HTMLS = $(MARKDOWNS:.md=.html) 4 | 5 | .SUFFIXES: .md .html 6 | 7 | all: $(HTMLS) 8 | 9 | .md.html: 10 | markdown $< > $@ 11 | -------------------------------------------------------------------------------- /docs/building.html: -------------------------------------------------------------------------------- 1 |

Building the Code and Programming the Chip

2 | 3 |

This package contains Atmel Studio 6.2 project files as well as a Makefile for 4 | building the demonstration executable.

5 | 6 |

This guide is written assuming some experience in working with AVR Microcontrollers and C. Check online for "hello world" style educational tutorials if desired.

7 | 8 |

Sound output settings can be customized with tinytune/settings.h if you have a different clock speed or wish to change the number of voices/sample rate.

9 | 10 |

Running the Demo

11 | 12 |

General Setup

13 | 14 |

The demo, tinytunetest.c builds several song files, and can be modified to play specific ones by changing the #define test[SONGNAME] line of the file.

15 | 16 |
// Adding this line should play a bit of music familiar to those who have played
 17 | // the NES game Faxanadu, this one is the default for no particular reason.
 18 | #define TEST_FAXANADU
 19 | 
 20 | // This will play the familiar dungeon theme from the Legend of Zelda.
 21 | #define TEST_ZELDA
 22 | 
 23 | // And so forth, see tinytune_test.c for more defined songs.
 24 | 
25 | 26 |

Makefile based build, on Linux

27 | 28 |

On Debian based distros such as Mint or Ubuntu, the Arduino development package 29 | covers all dependencies, so I recommend installing it if you do not have a dev environment 30 | set up already.

31 | 32 |
sudo apt-get install arduino
 33 | 
34 | 35 |

First, edit the Makefile to specify your MCU programming device, so that AVRDude 36 | can program your chip. Mine's an AVRISP Mkii.

37 | 38 |

This documentation assumes that you have UDEV set up for non-root access to your programming device. If not, run make programfuses_ and make program with sudo.

39 | 40 |

There are threads online documenting this but be aware that UDEV rules have 41 | changed a bit on newer distros.

42 | 43 |

On Linux Mint 17, I am using this udev rule:

44 | 45 |
...
 46 | 
47 | 48 |

It is likely that it would work on ubuntu as well.

49 | 50 |

Near the top, find the AVRDUDE_PROGRAMMER definition. E.g.

51 | 52 |
AVRDUDE_PROGRAMMER = avrisp2
 53 | AVRDUDE_PORT = usb
 54 | 
55 | 56 |

Change these to match your particular hardware and save. See the AVRDude documentation for 57 | programmer types.

58 | 59 |

From the tinytune_test path, run:

60 | 61 |
make clean&& make program_fuses&& make program
 62 | 
63 | 64 |

If all goes well, this should convert the demo songs to code, build the project, 65 | and program the fuses and image onto your device. make program_fuses need only be done 66 | once per chip, unless you've changed the fuse settings.

67 | 68 |

At this point, your device should also be outputing some sound on Pin 3.

69 | 70 |

Atmel Studio 6.2 based build on Windows

71 | 72 |

Prerequisites

73 | 74 |

[Atmel Studio 6.2] (http://www.atmel.com/tools/atmelstudio.aspx) must be installed, as well as Python (I'm using 2.7.8, not sure if Python 3.x works).

75 | 76 |

For the Renoise song importer to work, [Python] (https://www.python.org/download) must be installed and [included in the PATH environment variable for windows] (http://superuser.com/questions/143119/how-to-add-python-to-the-windows-path).

77 | 78 |

Open tinytunetest/tinytunetest.atsln in Atmel Studio.

79 | 80 |

Make sure the build type is set to Release.

81 | 82 |

Build the project (F7)

83 | 84 |

Under the Tools menu, open the Device Programming dialog.

85 | 86 |

Program your device's fuses (optionally), under the Fuses tab.

87 | 88 |

These are the settings that I'm using:

89 | 90 |
SELFPRGEN = [ ]
 91 | RSTDISBL = [ ]
 92 | DWEN = [ ]
 93 | SPIEN = [X]
 94 | WDTON = [ ]
 95 | EESAVE = [ ]
 96 | BODLEVEL = DISABLED
 97 | CKDIV8 = [ ]
 98 | CKOUT = [ ]
 99 | SUT_CKSEL = PLLCLK_16KCK_14CK_4MS
100 | 
101 | EXTENDED = 0xFF (valid)
102 | HIGH = 0xDF (valid)
103 | LOW = 0xD1 (valid)
104 | 
105 | 106 |

The important thing is to have:

107 | 108 |
SUT_CKSEL = PLLCLK_16KCK_14CK_4MS
109 | 
110 | 111 |

Which enables the 16MHz cpu clock and the fast 64MHz PLL clock.

112 | 113 |

Continue to the Production File tab on the device programming dialog. Select avrtinytune\tinytunetest\Release\tinytune_test.elf and program the device.

114 | 115 |

At this point, there should be PWM audio playing on Pin 3.

116 | -------------------------------------------------------------------------------- /docs/building.md: -------------------------------------------------------------------------------- 1 | Building the Code and Programming the Chip 2 | === 3 | This package contains Atmel Studio 6.2 project files as well as a Makefile for 4 | building the demonstration executable. 5 | 6 | This guide is written assuming some experience in working with AVR Microcontrollers and C. Check online for "hello world" style educational tutorials if desired. 7 | 8 | Sound output settings can be customized with tinytune/settings.h if you have a different clock speed or wish to change the number of voices/sample rate. 9 | 10 | Running the Demo 11 | --- 12 | ### General Setup 13 | The demo, tinytune_test.c builds several song files, and can be modified to play specific ones by changing the #define test_[SONGNAME] line of the file. 14 | 15 | // Adding this line should play a bit of music familiar to those who have played 16 | // the NES game Faxanadu, this one is the default for no particular reason. 17 | #define TEST_FAXANADU 18 | 19 | // This will play the familiar dungeon theme from the Legend of Zelda. 20 | #define TEST_ZELDA 21 | 22 | // And so forth, see tinytune_test.c for more defined songs. 23 | 24 | 25 | ### Makefile based build, on Linux 26 | On Debian based distros such as Mint or Ubuntu, the Arduino development package 27 | covers all dependencies, so I recommend installing it if you do not have a dev environment 28 | set up already. 29 | 30 | sudo apt-get install arduino 31 | 32 | First, edit the Makefile to specify your MCU programming device, so that [AVRDude](http://www.nongnu.org/avrdude/) 33 | can program your chip. Mine's an AVRISP Mkii. 34 | 35 | This documentation assumes that you have UDEV set up for non-root access to your programming device. If not, run _make program_fuses_ and _make program_ with sudo. 36 | 37 | There are threads online [documenting this](http://www.avrfreaks.net/forum/tut-hard-use-avr-dragon-linux-without-being-root?skey=avr%20isp%20udev) but be aware that UDEV rules have 38 | changed a bit on newer distros. 39 | 40 | On Linux Mint 17, I am using this udev rule: 41 | 42 | ... 43 | 44 | It is likely that it would work on ubuntu as well. 45 | 46 | Near the top, find the AVRDUDE_PROGRAMMER definition. E.g. 47 | 48 | AVRDUDE_PROGRAMMER = avrisp2 49 | AVRDUDE_PORT = usb 50 | 51 | Change these to match your particular hardware and save. See the [AVRDude documentation](http://www.nongnu.org/avrdude/user-manual/avrdude_4.html) for 52 | programmer types. 53 | 54 | From the tinytune_test path, run: 55 | 56 | make clean&& make program_fuses&& make program 57 | 58 | If all goes well, this should convert the demo songs to code, build the project, 59 | and program the fuses and image onto your device. make program_fuses need only be done 60 | once per chip, unless you've changed the fuse settings. 61 | 62 | At this point, your device should also be outputing some sound on Pin 3. 63 | 64 | ### Atmel Studio 6.2 based build on Windows 65 | 66 | #### Prerequisites 67 | [Atmel Studio 6.2] (http://www.atmel.com/tools/atmelstudio.aspx) must be installed, as well as Python (I'm using 2.7.8, not sure if Python 3.x works). 68 | 69 | For the Renoise song importer to work, [Python] (https://www.python.org/download) must be installed and [included in the PATH environment variable for windows] (http://superuser.com/questions/143119/how-to-add-python-to-the-windows-path). 70 | 71 | 72 | Open tinytune_test/tinytune_test.atsln in Atmel Studio. 73 | 74 | Make sure the build type is set to Release. 75 | 76 | Build the project (F7) 77 | 78 | Under the Tools menu, open the Device Programming dialog. 79 | 80 | 81 | 82 | Program your device's fuses (optionally), under the Fuses tab. 83 | 84 | These are the settings that I'm using: 85 | 86 | SELFPRGEN = [ ] 87 | RSTDISBL = [ ] 88 | DWEN = [ ] 89 | SPIEN = [X] 90 | WDTON = [ ] 91 | EESAVE = [ ] 92 | BODLEVEL = DISABLED 93 | CKDIV8 = [ ] 94 | CKOUT = [ ] 95 | SUT_CKSEL = PLLCLK_16KCK_14CK_4MS 96 | 97 | EXTENDED = 0xFF (valid) 98 | HIGH = 0xDF (valid) 99 | LOW = 0xD1 (valid) 100 | 101 | The important thing is to have: 102 | 103 | SUT_CKSEL = PLLCLK_16KCK_14CK_4MS 104 | 105 | Which enables the 16MHz cpu clock and the fast 64MHz PLL clock. 106 | 107 | Continue to the Production File tab on the device programming dialog. Select avr_tinytune\tinytune_test\Release\tinytune_test.elf and program the device. 108 | 109 | At this point, there should be PWM audio playing on Pin 3. -------------------------------------------------------------------------------- /docs/circuit.html: -------------------------------------------------------------------------------- 1 |

Hardware Circuit Setup

2 | 3 |

The hardware configurations for this chip are quite simple.

4 | 5 |

Basic Headphone Output

6 | 7 |

This layout works, but I do not recommend it very highly.

8 | 9 |

Basic layout

10 | 11 |

The only additional components are a 10k resistor pulling up the reset pin and a 220 ohm resistor limiting current to the speaker. The reset pull up seems to be practically optional, if you are feeling lazy or just want to make sure it works.

12 | 13 |

Basic Output with LowPass filter

14 | 15 |

This adds an RC LowPass filter as well as an electrolytic capacitor (value doesn't matter much) to eliminate any DC current to the output. I recommend something along these lines. Especially if you wish to run the output into powered speakers or an amplifier. An additional potentiometer can be added at the output point (before the speakers) for volume control, of course.

16 | 17 |

Basic layout

18 | 19 |

The resistor and capacitor values can be fudged quite a bit, but the example values will have a roll-off around 16KHz which is nicely in the human audible range, and happens to match the components I had laying around. If you have different capacitors and resistors, they can be used for a filter in the 1 - 20KHz range. There is a convenient online RC Filter calculator here.

20 | -------------------------------------------------------------------------------- /docs/circuit.md: -------------------------------------------------------------------------------- 1 | Hardware Circuit Setup 2 | === 3 | 4 | The hardware configurations for this chip are quite simple. 5 | 6 | Basic Headphone Output 7 | --- 8 | 9 | This layout works, but I do not recommend it very highly. 10 | 11 | ![Basic layout](images/super_basic.png) 12 | 13 | The only additional components are a 10k resistor pulling up the reset pin and a 220 ohm resistor limiting current to the speaker. The reset pull up seems to be practically optional, if you are feeling lazy or just want to make sure it works. 14 | 15 | Basic Output with LowPass filter 16 | --- 17 | 18 | This adds an RC LowPass filter as well as an electrolytic capacitor (value doesn't matter much) to eliminate any DC current to the output. I recommend something along these lines. Especially if you wish to run the output into powered speakers or an amplifier. An additional potentiometer can be added at the output point (before the speakers) for volume control, of course. 19 | 20 | ![Basic layout](images/low_pass.png) 21 | 22 | The resistor and capacitor values can be fudged quite a bit, but the example values will have a roll-off around 16KHz which is nicely in the human audible range, and happens to match the components I had laying around. If you have different capacitors and resistors, they can be used for a filter in the 1 - 20KHz range. There is a convenient online RC Filter calculator [here](http://www.ekswai.com/en_lowpass.htm). 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/images/low_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/docs/images/low_pass.png -------------------------------------------------------------------------------- /docs/images/low_pass.sch: -------------------------------------------------------------------------------- 1 | v 20130925 2 2 | C 46900 44500 1 0 0 ATtiny25-1.sym 3 | { 4 | T 47175 47950 5 10 0 0 0 0 1 5 | device=ATtiny85 6 | T 48000 45950 5 10 1 1 0 0 1 7 | refdes=U? 8 | T 47175 47150 5 10 0 0 0 0 1 9 | footprint=so8 10 | T 46900 44500 5 10 0 0 0 0 1 11 | netname=ATTiny85 12 | } 13 | C 48900 46800 1 0 0 vcc-1.sym 14 | { 15 | T 49200 46800 5 10 1 1 0 0 1 16 | value=5V 17 | } 18 | C 49000 43900 1 0 0 gnd-1.sym 19 | { 20 | T 48900 43700 5 10 1 1 0 0 1 21 | netname=GND 22 | } 23 | C 47400 47300 1 0 0 resistor-1.sym 24 | { 25 | T 47700 47700 5 10 0 0 0 0 1 26 | device=RESISTOR 27 | T 47600 47600 5 10 1 1 0 0 1 28 | refdes=330 29 | } 30 | N 48500 45100 49100 45100 4 31 | N 49100 45100 49100 46800 4 32 | N 48500 44900 50100 44900 4 33 | N 49100 44900 49100 44200 4 34 | N 46900 44900 46200 44900 4 35 | N 46200 44900 46200 47400 4 36 | N 46200 47400 47400 47400 4 37 | C 50600 45500 1 0 0 speaker-1.sym 38 | { 39 | T 52600 48000 5 10 0 0 0 0 1 40 | device=SPEAKER 41 | T 50500 47500 5 10 1 1 0 0 1 42 | refdes=HeadPhones 8 ohm 43 | } 44 | N 48300 47400 50600 47400 4 45 | N 50600 46200 50100 46200 4 46 | N 50100 46200 50100 44900 4 47 | N 46900 44700 46600 44700 4 48 | N 46600 44700 46600 46600 4 49 | C 47200 46500 1 0 0 resistor-1.sym 50 | { 51 | T 47500 46900 5 10 0 0 0 0 1 52 | device=RESISTOR 53 | T 47400 46800 5 10 1 1 0 0 1 54 | refdes=10K 55 | } 56 | N 46600 46600 47200 46600 4 57 | N 48100 46600 49100 46600 4 58 | C 48900 47700 1 90 0 capacitor-1.sym 59 | { 60 | T 48200 47900 5 10 0 0 90 0 1 61 | device=CAPACITOR 62 | T 48500 48500 5 10 1 1 180 0 1 63 | refdes=C 30nF 64 | T 48000 47900 5 10 0 0 90 0 1 65 | symversion=0.1 66 | } 67 | C 49400 47200 1 0 0 capacitor-3.sym 68 | { 69 | T 49600 47900 5 10 0 0 0 0 1 70 | device=POLARIZED_CAPACITOR 71 | T 49600 47700 5 10 0 1 0 0 1 72 | refdes=C 73 | T 49600 48100 5 10 0 0 0 0 1 74 | symversion=0.1 75 | } 76 | C 48800 49200 1 180 0 gnd-1.sym 77 | { 78 | T 48900 49400 5 10 1 1 180 0 1 79 | netname=GND 80 | } 81 | N 48700 47700 48700 47400 4 82 | N 48700 48600 48700 48900 4 83 | -------------------------------------------------------------------------------- /docs/images/super_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/docs/images/super_basic.png -------------------------------------------------------------------------------- /docs/images/super_basic.sch: -------------------------------------------------------------------------------- 1 | v 20130925 2 2 | C 47000 44500 1 0 0 ATtiny25-1.sym 3 | { 4 | T 47275 47950 5 10 0 0 0 0 1 5 | device=ATtiny25 6 | T 48100 45950 5 10 1 1 0 0 1 7 | refdes=U? 8 | T 47275 47150 5 10 0 0 0 0 1 9 | footprint=so8 10 | } 11 | C 48900 46800 1 0 0 vcc-1.sym 12 | { 13 | T 49200 46800 5 10 1 1 0 0 1 14 | value=5V 15 | } 16 | C 49000 43900 1 0 0 gnd-1.sym 17 | { 18 | T 48900 43700 5 10 1 1 0 0 1 19 | netname=GND 20 | } 21 | C 47400 47300 1 0 0 resistor-1.sym 22 | { 23 | T 47700 47700 5 10 0 0 0 0 1 24 | device=RESISTOR 25 | T 47600 47600 5 10 1 1 0 0 1 26 | refdes=220 27 | } 28 | N 48600 45100 49100 45100 4 29 | N 49100 45100 49100 46800 4 30 | N 48600 44900 50100 44900 4 31 | N 49100 44900 49100 44200 4 32 | N 47000 44900 46200 44900 4 33 | N 46200 44900 46200 47400 4 34 | N 46200 47400 47400 47400 4 35 | C 50300 45500 1 0 0 speaker-1.sym 36 | { 37 | T 52300 48000 5 10 0 0 0 0 1 38 | device=SPEAKER 39 | T 50200 47500 5 10 1 1 0 0 1 40 | refdes=HeadPhones 8ohm 41 | } 42 | N 48300 47400 50300 47400 4 43 | N 50300 46200 50100 46200 4 44 | N 50100 46200 50100 44900 4 45 | N 47000 44700 46600 44700 4 46 | N 46600 44700 46600 46600 4 47 | C 47200 46500 1 0 0 resistor-1.sym 48 | { 49 | T 47500 46900 5 10 0 0 0 0 1 50 | device=RESISTOR 51 | T 47400 46800 5 10 1 1 0 0 1 52 | refdes=10K 53 | } 54 | N 46600 46600 47200 46600 4 55 | N 48100 46600 49100 46600 4 56 | -------------------------------------------------------------------------------- /docs/song_setup.html: -------------------------------------------------------------------------------- 1 |

Song Setup and Import Guide

2 | 3 |

Coming soon.

4 | -------------------------------------------------------------------------------- /docs/song_setup.md: -------------------------------------------------------------------------------- 1 | Song Setup and Import Guide 2 | === 3 | 4 | Coming soon. 5 | 6 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | *NOTE* TinyTune has been superseded by DuinoTune which has more updates, compatibility, and better song conversion. 2 | === 3 | 4 | Avr TinyTune 5 | === 6 | 7 | Avr Tinytune is a multi-voice, mult-timbral audio synthesis and song playback library targeting the [Atmel ATtiny85 microcontroller](http://www.atmel.com/devices/attiny85.aspx), requiring minimal additional electronic components for use in projects. 8 | 9 | What Exactly Does It Do? 10 | --- 11 | 12 | The C library consists of two somewhat independent parts; A multi-voice synthesizer that uses Fast PWM to simulate 8-bit analog output, and an interrupt driven background-song playback system that plays note and controller data out of chip Flash memory (progmem). 13 | 14 | There is also an import utility that generates embeddable code representations of songs, using the save files of the [Renoise](http://www.renoise.com/) tracker-based music composition program as input. Renoise offers a demo version that is usable with this project; though I heartily encourage supporting their excellent software if you find it useful. 15 | 16 | Example video, running into an oscilloscope: https://www.youtube.com/watch?v=PtxxCKs822M 17 | 18 | Update 19 | --- 20 | * Now with much more stable playback in the face of occasional buffer under-runs. Sounds like circuit-bent mayhem at 65000hz. 21 | * Song `TEST_ENV` at `#define SAMPLE_BUFFER 30` and `#define SAMPLE_RATE 78930UL` in settings is kind of bonkers. 22 | 23 | * Renoise 3.01 or better is required for song import. If updating a song from an older version of Renoise, the volume envelopes in the song need to be "Upgraded" in the envelope editing UI. The importer won't pick them up otherwise. 24 | 25 | * Atmega8 support added via a patch contributed by Lucas Berezy! Output is on PB3, or pin 17. 26 | 27 | Features 28 | --- 29 | 30 | ### Hardware 31 | 32 | * **No** external crystal or oscillator required, though they can probably be used (untested). The ATtiny line of microcontrollers include a fast PLL peripheral clock (PCK) that runs at 64MHz, providing a cpu clock of 16MHz, and an 8-bit duty-cycle resolution PWM period of 250KHz. 33 | * Quality wise, this is especially nice since the PLL-generated high PWM frequency is easier to filter and introduces less acoustic distortion. 34 | * The cpu clock may potentially be able to be boosted to 20MHz, for more polyphony or higher sample rate, with no additional components, by setting the OSCCAL register to its maximum of 0xFF _(I have not tested this and it is not directly supported in code settings yet)_ 35 | 36 | * Requires only an external resistor for very basic use, e.g. driving small headphones _badly_ (since they act as a sort of physical low pass filter). _Against **all** better judgment, even this can be skipped if you don't mind blowing up your chip (by output current overload) or your eardrum every now and then._ 37 | 38 | * A resistor capacitor lowpass filter can be used to smooth the PWM output into a reasonably clean audio signal, suitable for driving small headphones or input into an amplifier circuit. 39 | 40 | ### Synthesis 41 | * Can play as many simultaneous voices as the MCU speed and RAM can accommodate 42 | * Offers the following voice types: 43 | * PWM (square wave), with adjustable duty cycle 44 | * Triangle/Saw wave, continuously adjustable between the two waveforms 45 | * Noise channel. This is currently usable, but slow. Improvements are planned 46 | * Adjustable playback volume 47 | * Interrupt based, and can run in the background 48 | * Bit Crush 49 | * Reduces output waveform bit depth. Makes most sense on triangle waves. Try a BitCrush of 4 on a TRI wave for that old-school NES tri sound. 50 | 51 | ### Song playback 52 | * Arbitrary envelopes, including sustain and release 53 | * Portamento and pitch-glides 54 | * Pattern based playback, for space efficiency 55 | *repeated patterns of music do not take much additional Flash ram 56 | * Song size limited mostly by Flash capacity 57 | * Interrupt based, and can run in the background 58 | 59 | ### Renoise import 60 | * Supports arbitrary pattern lengths 61 | * Instrument envelope import 62 | * Imports pitch glide and bend commands 63 | 64 | Limitations 65 | --- 66 | * Noise channel only supports 4-bit volume (life is hard without hardware multiply) 67 | * Not all envelope parameters are supported (no loops) 68 | * There are still some bugs in synthesis 69 | * Output pin is hard-coded to Pin 3 (OC1B) It should be changeable, but it is untested. 70 | * Importer does not set waveform types per instrument yet. They must be set manually when song playback begins (or after if you want). 71 | * There is no technical limitation preventing this, it just hasn't been done yet 72 | * I would envision setting special Renoise instrument names that represent the voice type, e.g. "PWM duty:64" that the importer interprets into song data. 73 | * There are some parts of code, particularly in the importer, that I am not especially proud of. A refactoring sweep, knowing what the implementation process had to teach, would do the code base well 74 | * It should be possible to have this run with an external crystal resonator for predictable sound tuning, but I have not tried it yet. The output would also need to be moved to a different pin, since it overlaps one the ATtiny85's resonator pins. Patches welcome. 75 | * Envelopes and pitch bends will only function while a 'song' is playing, initiated by playSong(). For non-song audio synthesis, the caller will have to change volumes and pitches as needed. 76 | * This would be a reasonable feature for addition 77 | 78 | 79 | Documentation and Demos 80 | --- 81 | * See the tinytune_test directory for an example of song playback and conversion. 82 | * See the docs directory for further information. 83 | * See [tinytune/tinytune.h](tinytune/tinytune.h) for per-function comments. 84 | 85 | Hacking 86 | --- 87 | This is a hobby project, and my MCU and electronics experience could always use input. Feel free to make suggestions, point out document errors and unclearness, and, best still, submit patches or feature additions to the project. 88 | 89 | Also feel free to drop me a line if you use this library! I'm always curious to see how it 90 | comes in handy. My original intent was to augment [one of these guys](http://www.otamatone.com/) with super enhanced sound, but I still haven't gotten around to it. 91 | 92 | License 93 | --- 94 | This project is provided under an [MIT License](LICENSE) 95 | -------------------------------------------------------------------------------- /test_songs/demo1.xrns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/demo1.xrns -------------------------------------------------------------------------------- /test_songs/env_test.xrns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/env_test.xrns -------------------------------------------------------------------------------- /test_songs/faxanadu.xrns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/faxanadu.xrns -------------------------------------------------------------------------------- /test_songs/instruments/Square 0x80.xrni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/instruments/Square 0x80.xrni -------------------------------------------------------------------------------- /test_songs/instruments/Square 0xbf.xrni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/instruments/Square 0xbf.xrni -------------------------------------------------------------------------------- /test_songs/instruments/Square 0xdd.xrni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/instruments/Square 0xdd.xrni -------------------------------------------------------------------------------- /test_songs/instruments/nesTri.xrni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/instruments/nesTri.xrni -------------------------------------------------------------------------------- /test_songs/instruments/noise.xrni: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/instruments/noise.xrni -------------------------------------------------------------------------------- /test_songs/noise.xrns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/noise.xrns -------------------------------------------------------------------------------- /test_songs/pitch.xrns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/pitch.xrns -------------------------------------------------------------------------------- /test_songs/zelda.xrns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blakelivingston/avr_tinytune/60308751662200ff407dd9454b7fe010401ed7d6/test_songs/zelda.xrns -------------------------------------------------------------------------------- /tinytune/settings.h: -------------------------------------------------------------------------------- 1 | // CPU clock rate 2 | #if defined (__AVR_ATmega8__) 3 | #define F_CPU 8000000 4 | #else 5 | #define F_CPU 16000000 6 | #endif 7 | // Number of bytes to pre-calculate. Not conclusively shown that this helps much. 8 | // Use even numbers unless you like high pitched howls. 9 | #define SAMPLE_BUFFER 16 10 | // Sample clock updates at this rate in Hz 11 | #if defined (__AVR_ATmega8__) 12 | #define SAMPLE_RATE 14000UL 13 | #else 14 | // Go ahead and abuse this number in the 55000 and above range if you get bored. Glitches may combine 15 | // amusingly with sample buffer size changes. It's like the chip will break. 16 | // Some sounds take on an almost vocal quality in their distortion at 65000hz 17 | #define SAMPLE_RATE 32000UL 18 | #endif 19 | // Max number of voices 20 | #define N_VOICES 5 21 | // Right shift divide for output waveform. Use for course global volume setting 22 | #define OUTPUT_SCALE_SHIFT 3 -------------------------------------------------------------------------------- /tinytune/tinytune.c: -------------------------------------------------------------------------------- 1 | #include "tinytune/settings.h" 2 | #define SAMPLE_CLOCK (F_CPU / 8) 3 | #define SAMPLE_CLOCK_DIVIDER SAMPLE_CLOCK/SAMPLE_RATE 4 | #define TIMEOUT F_CPU / SAMPLE_RATE 5 | 6 | #define MAX(x, y) (((x) > (y)) ? (x) : (y)) 7 | #define MIN(x, y) (((x) < (y)) ? (x) : (y)) 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "tinytune/tinytune.h" 16 | 17 | struct TTVOICE voices[N_VOICES]; 18 | volatile uint32_t sample_cnt = 0; 19 | int16_t sample_buffer[SAMPLE_BUFFER]; 20 | uint8_t sample_buf_clock = SAMPLE_BUFFER - 1; 21 | uint8_t sample_update_idx = 0; 22 | 23 | void _setDutyTRI(struct TTVOICE* v, uint8_t duty); 24 | 25 | // Gosh.. a mul instruction would be great. 26 | // May be best to precompute this when volume is set, since that can be slow.. 27 | inline int16_t four_bit_scale(int16_t input, uint8_t scale) { 28 | if (scale == 0x10) 29 | return input; 30 | if (scale == 0) 31 | return 0; 32 | int16_t output = 0; 33 | asm volatile( 34 | "swap %[scale]\n" 35 | "lsl %[scale]\n" 36 | "brcc j1\n" 37 | "add %A[output], %A[input]\n" 38 | "adc %B[output], %B[input]\n" 39 | "j1:\n" 40 | "lsl %A[output]\n" 41 | "rol %B[output]\n" 42 | "lsl %[scale]\n" 43 | "brcc j2\n" 44 | "add %A[output], %A[input]\n" 45 | "adc %B[output], %B[input]\n" 46 | "j2:\n" 47 | "lsl %A[output]\n" 48 | "rol %B[output]\n" 49 | "lsl %[scale]\n" 50 | "brcc j3\n" 51 | "add %A[output], %A[input]\n" 52 | "adc %B[output], %B[input]\n" 53 | "j3:\n" 54 | "lsl %A[output]\n" 55 | "rol %B[output]\n" 56 | "lsl %[scale]\n" 57 | "brcc j4\n" 58 | "add %A[output], %A[input]\n" 59 | "adc %B[output], %B[input]\n" 60 | "j4:\n" 61 | : 62 | [output] "+r" (output): 63 | [input] "r" (input), 64 | [scale] "r" (scale) 65 | ); 66 | return output >> 4; 67 | } 68 | 69 | void initPLL(void) { 70 | #if !defined (__AVR_ATmega8__) 71 | PLLCSR = 1 << PLLE; //Enable PLL 72 | _delay_us(100); // Wait for a sync; 73 | while (!(PLLCSR & (1 << PLOCK))) { 74 | }; 75 | // PLL Synced 76 | PLLCSR |= (1 << PCKE); // Enable Asynchronous PCK mode. 77 | #endif 78 | } 79 | 80 | void initSampleInterrupt(void) { 81 | #if defined (__AVR_ATmega8__) 82 | TIMSK |= (1 << TOIE1); 83 | // fast pwm ( WGM13, 12, 11,10) for TOP controlled by OCR1A 84 | TCCR1B |= (1 << WGM12 ) | (1 << WGM13); 85 | TCCR1A |= (1 << WGM10) | (1 << WGM11); 86 | TCCR1B |= (1 << CS11); // CS11 for /8 divider 87 | OCR1A = SAMPLE_CLOCK_DIVIDER; 88 | sei(); 89 | #else 90 | // Clock : Sys/8 (2mhz) 91 | // Fast PWM mode (|WGM0-2) 92 | TCCR0A = (1 << WGM00) | (1 << WGM01); 93 | TCCR0B = (1 << CS01) | (1 << WGM02); 94 | // Enable interrupt on overflow. 95 | TIMSK |= (1 << TOIE0); 96 | // In fast PWM mode, the timer overflows at OCR0A 97 | // Our timer / OCR0A = SAMPLE RATE 98 | // Do make sure this is under 255.. 99 | OCR0A = SAMPLE_CLOCK_DIVIDER; 100 | 101 | sei(); 102 | // Enable interrupts 103 | #endif 104 | } 105 | 106 | void do_song_tick(void); 107 | 108 | #define SYNTH_TASK 1 109 | #define SONG_TASK 2 110 | volatile uint8_t task_bits = 0; 111 | #if defined (__AVR_ATmega8__) 112 | ISR(TIMER1_OVF_vect) 113 | #else 114 | ISR(TIMER0_OVF_vect) 115 | #endif 116 | { 117 | #if defined (__AVR_ATmega8__) 118 | OCR2 = sample_buffer[sample_buf_clock++]; 119 | #else 120 | OCR1B = sample_buffer[sample_buf_clock++]; 121 | #endif 122 | ++sample_cnt; 123 | ++song_info.tick_smp_count; 124 | static bool sample_processing = 0; 125 | 126 | 127 | 128 | if (sample_buf_clock == SAMPLE_BUFFER) 129 | sample_buf_clock = 0; 130 | uint8_t fill_sample_buffer = 0; 131 | if (sample_buf_clock == 0) { 132 | fill_sample_buffer = 1; 133 | sample_update_idx = SAMPLE_BUFFER / 2; 134 | } else if (sample_buf_clock == SAMPLE_BUFFER / 2) { 135 | fill_sample_buffer = 1; 136 | sample_update_idx = 0; 137 | } 138 | 139 | if ((!sample_processing) && fill_sample_buffer && !(task_bits & SYNTH_TASK)) { 140 | sample_processing = 1; 141 | task_bits |= SYNTH_TASK; 142 | PORTB |= 1; 143 | sei(); 144 | //memset(&sample_buffer[sample_update_idx], 0, SAMPLE_BUFFER); 145 | for (uint8_t i = 0; i < N_VOICES; ++i) { 146 | if (voices[i].enabled) 147 | voices[i]._getSample(&voices[i]); 148 | } 149 | for (uint8_t i = 0; i < SAMPLE_BUFFER / 2; ++i) { 150 | int16_t tmp = sample_buffer[sample_update_idx + i] >> OUTPUT_SCALE_SHIFT; 151 | tmp = MIN(tmp, 0x7f); 152 | tmp = MAX(tmp, -0x7f); 153 | sample_buffer[sample_update_idx + i] = tmp + 0x80; 154 | } 155 | PORTB &= ~1; 156 | task_bits &= ~SYNTH_TASK; 157 | sample_processing = 0; 158 | return; 159 | } 160 | 161 | static bool pattern_processing = 0; 162 | if(!pattern_processing) { 163 | pattern_processing = 1; 164 | if (song_info.playing && song_info.tick_smp_count 165 | > song_info.samples_per_tick && !(task_bits & SONG_TASK) && !(task_bits 166 | & SYNTH_TASK)) { 167 | //task_bits &= ~SYNTH_TASK; 168 | // Re enable sample interrupts while we 'do' the song tick. 169 | // Note sets have division and stuff, so it might take a little while. 170 | // If it takes longer than the tick itself, then you have problems. 171 | task_bits |= SONG_TASK; 172 | sei(); 173 | PORTB |= 2; 174 | song_info.tick_smp_count = 0; 175 | do_song_tick(); 176 | PORTB &= ~2; 177 | task_bits &= ~SONG_TASK; 178 | } 179 | pattern_processing = 0; 180 | } 181 | } 182 | 183 | void waitMS(uint16_t ms) { 184 | uint32_t dest = sample_cnt + (SAMPLE_RATE / 1000) * ms; 185 | do { 186 | } while (sample_cnt < dest); 187 | } 188 | void initPWMB(void) { 189 | #if defined (__AVR_ATmega8__) 190 | // set up timer 2 - this will be the output PWM 191 | TCCR2 |= ((1 << WGM21) | (1 << WGM20)); // Fast-PWM (p.115) 192 | TCCR2 |= (1 << COM21); // non-inverting OC2 pin output (p115) 193 | TCCR2 &= ~(1 << COM20); 194 | TCCR2 |= (1 << CS10); // prescale 1x 195 | OCR2 = 0; 196 | DDRB |= (1 << DDB3); // Enable OC2 (PB3) as output 197 | #else 198 | TCCR1 = (1 << CS10); // Run at PCK/1 199 | GTCCR = (1 << PWM1B) | (1 << COM1B0); //Enable PWMB (pb4) 200 | DDRB |= (1 << PB4) | (1 << PB0) | (1 << PB1); // Output on pb4 201 | OCR1C = 0xff; 202 | #endif 203 | } 204 | 205 | void initTinyTune(void) { 206 | for (uint8_t i = 0; i < N_VOICES; ++i) 207 | initVoicePWM(i); 208 | initPLL(); 209 | initPWMB(); 210 | song_info.playing = 0; 211 | initSampleInterrupt(); 212 | } 213 | 214 | void _getSamplePWM(struct TTVOICE* v) { 215 | for (uint8_t i = 0; i < SAMPLE_BUFFER / 2; ++i) { 216 | int8_t outp = v->_s_volume; 217 | v->_err += v->hz; 218 | if (v->_err >= SAMPLE_RATE) { 219 | v->_err -= SAMPLE_RATE; 220 | } 221 | if (v->_err >= v->_params.pwm.duty_period) { 222 | outp = -outp; 223 | } 224 | 225 | sample_buffer[i + sample_update_idx] += outp; 226 | } 227 | } 228 | 229 | static inline uint8_t prnd(void) { 230 | static volatile uint8_t x = ~0; 231 | x += TCNT1; 232 | x ^= 0xb5; 233 | return x; 234 | } 235 | 236 | void _getSampleNOISE(struct TTVOICE* v) { 237 | int8_t val = v->_params.pwm.duty_period; 238 | uint16_t hz4 = v->hz << 4; 239 | for (uint8_t i = 0; i < SAMPLE_BUFFER / 2; ++i) { 240 | v->_err += hz4; 241 | if (v->_err >= SAMPLE_RATE) { 242 | v->_err -= SAMPLE_RATE; 243 | val = four_bit_scale(prnd(), (v->_s_volume >> 3)); 244 | } 245 | sample_buffer[sample_update_idx + i] += val; 246 | } 247 | v->_params.pwm.duty_period = val; 248 | } 249 | 250 | //void _getSamplePWM_ASM(struct TTVOICE* v) { 251 | //asm volatile ( 252 | //"samp_loop_p:\n" 253 | //"movw %[vol], %[s_vol]\n" 254 | //"cp %A[err], %A[s_rate]\n" 255 | //"cpc %B[err], %B[s_rate]\n" 256 | //"brlo no_new_cycle\n" 257 | //"sub %A[err], %A[s_rate]\n" 258 | //"sbc %B[err], %B[s_rate]\n" 259 | //"no_new_cycle:\n" 260 | //"add %A[err], %A[hz]\n" 261 | //"adc %B[err], %B[hz]\n" 262 | //"cp %A[err], %A[duty_pd]\n" 263 | //"cpc %B[err], %B[duty_pd]\n" 264 | //"brlo no_vol_flip\n" 265 | //"movw %[vol], %[ns_vol]\n" 266 | //"no_vol_flip:\n" 267 | //"ld __tmp_reg__, %a[out_buf]\n" 268 | //"add __tmp_reg__, %A[vol]\n" 269 | //"st %a[out_buf]+, __tmp_reg__\n" 270 | //"ld __tmp_reg__, %a[out_buf]\n" 271 | //"adc __tmp_reg__, %B[vol]\n" 272 | //"st %a[out_buf]+, __tmp_reg__\n" 273 | //"subi %[buf_len], 1\n" 274 | //"brne samp_loop_p\n": 275 | //[err] "+w" (v->_err): 276 | //[hz] "r" (v->hz), 277 | //[s_vol] "r" ((uint16_t)v->_s_volume), 278 | //[ns_vol] "r" ((uint16_t)-v->_s_volume), 279 | //[vol] "r" ((uint16_t) 0), 280 | //[s_rate] "r" ((uint16_t)SAMPLE_RATE), 281 | //[buf_len] "r" (SAMPLE_BUFFER / 2), 282 | //[out_buf] "e" (&sample_buffer[sample_update_idx]), 283 | //[duty_pd] "r" (v->_params.pwm.duty_period) 284 | //); 285 | //} 286 | 287 | void _getSampleTRI(struct TTVOICE* v) { 288 | uint8_t lp = SAMPLE_BUFFER / 2; 289 | uint8_t i = sample_update_idx; 290 | uint16_t err = v->_err; 291 | int16_t lev = v->_params.tri.level; 292 | int16_t fp_vol = v->_params.tri.fp_vol; 293 | uint16_t rise_p = v->_params.tri.rise_period; 294 | uint16_t rslp = v->_params.tri.rise_slp; 295 | uint16_t fslp = v->_params.tri.fall_slp; 296 | uint16_t bcrunch = v->bcrunch; 297 | do { 298 | 299 | if (err >= SAMPLE_RATE) { 300 | err -= SAMPLE_RATE; 301 | lev = fp_vol; 302 | } 303 | if (err < rise_p) { 304 | lev += rslp; 305 | } else { 306 | lev += fslp; 307 | } 308 | err += v->hz; 309 | sample_buffer[i++] += (lev >> 7) & bcrunch; 310 | } while (--lp != 0); 311 | v->_params.tri.level = lev; 312 | v->_err = err; 313 | } 314 | 315 | //void _getSampleTRI_ASM(struct TTVOICE* v) { 316 | //asm volatile ( 317 | //"samp_loop:\n" 318 | //"cp %A[err], %A[s_rate]\n" 319 | //"cp %B[err], %B[s_rate]\n" 320 | //"brlo skip_period\n" 321 | //"; Reset err term and level\n" 322 | //"sub %A[err], %A[s_rate]; reset err to period for countdown\n" 323 | //"sbc %B[err], %B[s_rate]; reset err to period for countdown\n" 324 | //"movw %[lev], %[fp_vol]\n" 325 | //"skip_period:\n" 326 | //"add %A[err], %A[hz]\n" 327 | //"adc %B[err], %B[hz]\n" 328 | //"cp %A[err],%A[rise_p]\n" 329 | //"cpc %B[err],%B[rise_p]\n" 330 | //"brlo rising\n" 331 | //"add %A[lev], %A[fall_slp]\n" 332 | //"adc %B[lev], %B[fall_slp]\n" 333 | //"rjmp done_slopes\n" 334 | //"rising:\n" 335 | //"add %A[lev], %A[rise_slp]\n" 336 | //"adc %B[lev], %B[rise_slp]\n" 337 | //"done_slopes:\n" 338 | //"movw r2,%[lev]\n" 339 | //"lsl r2\n" // signed right shift by 7 voodoo 340 | //"mov r2, r3\n" 341 | //"rol r3\n" 342 | //"sbc r3, r3\n" 343 | //"lsl r2\n" //This can be optimized into the above shift.. 344 | //"rol r3\n" 345 | //"ld __tmp_reg__, %a[out_buf]\n" 346 | //"add r2, __tmp_reg__\n" 347 | //"st %a[out_buf]+, r2\n" 348 | //"ld __tmp_reg__, %a[out_buf]\n" 349 | //"adc __tmp_reg__, r3\n" 350 | //"st %a[out_buf]+, __tmp_reg__\n" 351 | //"subi %[buf_len], 1\n" 352 | //"brne samp_loop\n": 353 | //[err] "+w" (v->_err), 354 | //[lev] "+w" (v->_params.tri.level): 355 | //[buf_len] "r" (SAMPLE_BUFFER / 2), 356 | //[out_buf] "e" (&sample_buffer[sample_update_idx]), 357 | //[s_rate] "r" ((uint16_t)SAMPLE_RATE), 358 | //[hz] "r" ((uint16_t)v->hz), 359 | //[fp_vol] "r" (v->_params.tri.fp_vol), 360 | //[rise_p] "r" (v->_params.tri.rise_period), 361 | //[rise_slp] "r" (v->_params.tri.rise_slp), 362 | //[fall_slp] "r" (v->_params.tri.fall_slp), 363 | //[bcrunch] "r" (v->bcrunch) 364 | //:"r2", "r3"); 365 | //} 366 | 367 | void _setDutyPWM(struct TTVOICE* v, uint8_t duty) { 368 | if (duty < 0x80) 369 | duty = 0x80 + (0x79 - duty); 370 | v->_params.pwm.duty = duty; 371 | v->_params.pwm.duty_period 372 | = (((uint32_t) duty * (uint32_t) SAMPLE_RATE) >> 8); 373 | } 374 | 375 | void _setPitchPWM(struct TTVOICE* v, uint16_t pitch) { 376 | v->hz = pitch; 377 | v->_period = SAMPLE_RATE / pitch; 378 | _setDutyPWM(v, v->_params.pwm.duty); 379 | } 380 | 381 | void _setDutyNOISE(struct TTVOICE* v, uint8_t duty) { 382 | 383 | } 384 | 385 | void _setPitchNOISE(struct TTVOICE* v, uint16_t pitch) { 386 | v->hz = pitch; 387 | } 388 | 389 | void _setPitchTRI(struct TTVOICE* v, uint16_t pitch) { 390 | v->hz = pitch; 391 | v->_period = SAMPLE_RATE / pitch; 392 | _setDutyTRI(v, v->_params.pwm.duty); 393 | } 394 | 395 | void _setEnable(struct TTVOICE* v, bool enable) { 396 | if (v->_envelope && !v->gliding) { 397 | if (enable) { 398 | v->enabled = enable; 399 | v->_env_volume = v->_envelope->starting_level << 7; 400 | v->_env_idx = 0; 401 | v->_env_ticks = 0; 402 | v->_env_ticks_left_for_node = v->_envelope->point_ticks[0]; 403 | v->sustaining = 0; 404 | } else { 405 | // note disables do note effect envelopes. 406 | // If the env volume hits zero the channel will be disabled. 407 | v->sustaining = 0; 408 | v->_env_ticks++; 409 | if (v->_env_ticks < v->_envelope->sustain_tick) 410 | v->_env_ticks = v->_envelope->sustain_tick + 1; 411 | } 412 | } else { 413 | v->enabled = enable; 414 | } 415 | } 416 | 417 | void _setVolume(struct TTVOICE* v, uint8_t volume) { 418 | v->volume = volume; 419 | if (v->_envelope) { 420 | v->_s_volume = ((v->_env_volume >> 7) * (volume >> 1)) >> 8; 421 | } else 422 | v->_s_volume = volume >> 1; 423 | } 424 | 425 | void _setDutyTRI(struct TTVOICE* v, uint8_t duty) { 426 | if (duty < 0x80) 427 | duty = 0x80 + (0x79 - duty); 428 | v->_params.tri.duty = duty; 429 | uint16_t rise_len = (((uint32_t) duty * v->_period) >> 8); 430 | rise_len = MAX(1, rise_len); 431 | rise_len = MIN(v->_period - 1, rise_len); 432 | v->_params.tri.rise_period 433 | = (((uint32_t) duty * (uint32_t) SAMPLE_RATE) >> 8); 434 | v->_params.tri.rise_slp = -(v->_params.tri.fp_vol << 1) / rise_len; 435 | v->_params.tri.fall_slp = (((int32_t) v->_params.tri.fp_vol << 1) 436 | / (v->_period - rise_len)); 437 | } 438 | 439 | void _setVolumeTRI(struct TTVOICE* v, uint8_t volume) { 440 | _setVolume(v, volume); 441 | v->_params.tri.fp_vol = -(v->_s_volume << 7); 442 | _setDutyTRI(v, v->_params.tri.duty); 443 | } 444 | 445 | void initVoiceCommon(struct TTVOICE* v) { 446 | v->enabled = 0; 447 | v->_err = 0; 448 | v->_setDuty(v, 0x80); 449 | v->_setVolume(v, 0xb0); 450 | v->_setPitch(v, 440); 451 | v->_setEnable(v, 0); 452 | v->_envelope = 0; 453 | v->gliding = 0; 454 | v->f_hz = 0; 455 | v->bcrunch = 0xffff; 456 | } 457 | 458 | void initVoicePWM(uint8_t voice) { 459 | struct TTVOICE* v = &voices[voice]; 460 | v->voice_type = TT_PWM; 461 | v->_getSample = &_getSamplePWM; 462 | v->_setEnable = &_setEnable; 463 | v->_setDuty = &_setDutyPWM; 464 | v->_setPitch = &_setPitchPWM; 465 | v->_setVolume = &_setVolume; 466 | initVoiceCommon(v); 467 | } 468 | 469 | void initVoiceTRI(uint8_t voice) { 470 | struct TTVOICE* v = &voices[voice]; 471 | v->voice_type = TT_TRI; 472 | v->_getSample = &_getSampleTRI; 473 | v->_setEnable = &_setEnable; 474 | v->_setDuty = &_setDutyTRI; 475 | v->_setPitch = &_setPitchTRI; 476 | v->_setVolume = &_setVolumeTRI; 477 | initVoiceCommon(v); 478 | } 479 | 480 | void initVoiceNOISE(uint8_t voice) { 481 | struct TTVOICE* v = &voices[voice]; 482 | v->voice_type = TT_NOISE; 483 | v->_getSample = &_getSampleNOISE; 484 | v->_setEnable = &_setEnable; 485 | v->_setDuty = &_setDutyNOISE; //Nop 486 | v->_setPitch = &_setPitchNOISE; 487 | initVoiceCommon(v); 488 | } 489 | 490 | void setVolume(uint8_t voice, uint8_t volume) { 491 | struct TTVOICE* v = &voices[voice]; 492 | v->_setVolume(v, volume); 493 | } 494 | void setDuty(uint8_t voice, uint8_t duty) { 495 | struct TTVOICE* v = &voices[voice]; 496 | v->_setDuty(v, duty); 497 | } 498 | 499 | void setPitch(uint8_t voice, uint16_t pitch) { 500 | struct TTVOICE* v = &voices[voice]; 501 | if (!v->gliding) 502 | v->_setPitch(v, pitch); 503 | else 504 | // If we're gliding, don't set the pitch now, set the destination. 505 | // The tick handler will call _setPitch. 506 | v->porta_pitch = pitch; 507 | } 508 | 509 | void setEnable(uint8_t voice, bool enable) { 510 | struct TTVOICE* v = &voices[voice]; 511 | v->_setEnable(v, enable); 512 | } 513 | 514 | void setBitCrunch(uint8_t voice, uint8_t crunch) { 515 | struct TTVOICE* v = &voices[voice]; 516 | v->bcrunch = 0xffff << (8 - crunch); 517 | } 518 | void setPortaRate(uint8_t voice, uint16_t p_rate) { 519 | struct TTVOICE* v = &voices[voice]; 520 | v->porta_rate = p_rate; 521 | } 522 | 523 | void setPorta(uint8_t voice, bool enable) { 524 | struct TTVOICE* v = &voices[voice]; 525 | v->gliding = enable; 526 | } 527 | 528 | // Call this with null to disable envelope. 529 | void setEnvelope(uint8_t voice, const struct TTENVELOPE* envelope) { 530 | struct TTVOICE* v = &voices[voice]; 531 | if (v->_envelope != envelope && envelope) { 532 | v->_envelope = envelope; 533 | v->_env_volume = v->_envelope->starting_level; 534 | v->_env_idx = 0; 535 | v->_env_ticks = 0; 536 | v->_env_ticks_left_for_node = v->_envelope->point_ticks[0]; 537 | v->sustaining = 0; 538 | } 539 | if (!envelope) { 540 | v->_envelope = envelope; 541 | } 542 | } 543 | 544 | //ticks_sec is in 12.4 fixed point 545 | void playSong(struct song_definition* song) { 546 | // Yeah, fixed point. 547 | uint16_t ticks_sec = ((song->bpm << 4) / 60) * song->rows_per_beat 548 | * song->ticks_per_row; 549 | song_info.song_def = song; 550 | song_info.pat_idx = 0; 551 | song_info.order_idx = 0; 552 | song_info.cur_pattern = pgm_read_byte(&song->pattern_order[0]); 553 | song_info.tick = 0; 554 | song_info.next_tick = 0; 555 | song_info.tick_smp_count = 0; 556 | song_info.samples_per_tick = ((uint32_t) SAMPLE_RATE << 4) / ticks_sec; 557 | song_info.playing = 1; 558 | } 559 | 560 | // 14.2 fixed point note hz from c4-b4 561 | const uint16_t PROGMEM pitch_table[] = { 1046, 1108, 1174, 1244, 1318, 1396, 1479, 562 | 1567, 1661, 1760, 1864, 1975 }; 563 | 564 | uint16_t get_pitch(uint8_t note_code) { 565 | // if this is too slow switch to 4.4 octave.note coding. 566 | // otherwise midi compatibility is nice. 567 | int8_t octave = (note_code / 12) - 7; 568 | uint8_t note = note_code % 12; 569 | uint16_t note_hz = pgm_read_word(&(pitch_table[note])); 570 | if (octave >= 0) { 571 | return note_hz << octave; 572 | } else { 573 | return note_hz >> -octave; 574 | } 575 | } 576 | 577 | void do_song_tick(void) { 578 | if (song_info.tick == 0) { 579 | char done_row = 0; 580 | // Porta must be re-enabled every row. 581 | for (int i = 0; i < N_VOICES; ++i) 582 | setPorta(i, 0); 583 | char 584 | * cur_pat = 585 | pgm_read_word(&(song_info.song_def->pattern_data[song_info.cur_pattern])); 586 | do { 587 | 588 | uint8_t code = pgm_read_byte(&(cur_pat[song_info.pat_idx++])); 589 | // Upper nibble is the voice id. 590 | uint8_t voice = code >> 4; 591 | // Lower nibble is the instruction. 592 | code = code & 0xf; 593 | switch (code) { 594 | case NOTE_ON_FULL_VOL: 595 | setVolume(voice, 0xff); 596 | case NOTE_ON: { 597 | uint8_t note = pgm_read_byte(&(cur_pat[song_info.pat_idx++])); 598 | // The top bit determines whether a volume follows the note-on. 599 | uint16_t pitch = get_pitch(note & 0x7f); 600 | setPitch(voice, pitch); 601 | setEnable(voice, 1); 602 | // if bit7 is set, trundle on into the volume code 603 | // Break otherwise. 604 | if ((note & 0x80) == 0) { 605 | //setVolume(voice, 0xff); 606 | break; 607 | } 608 | } 609 | case SET_VOL: { 610 | uint8_t volume = pgm_read_byte(&(cur_pat[song_info.pat_idx++])); 611 | setVolume(voice, volume); 612 | } 613 | break; 614 | case NOTE_OFF: 615 | setEnable(voice, 0); 616 | break; 617 | case ROW_ADV: { 618 | // We use voice for the number of rows to wait. 619 | // For more than 16.. wait twice. 620 | song_info.next_tick = (1 + voice) * song_info.song_def->ticks_per_row; 621 | done_row = 1; 622 | } 623 | break; 624 | case SET_ENV: { 625 | uint8_t env_id = pgm_read_byte(&(cur_pat[song_info.pat_idx++])); 626 | setEnvelope(voice, song_info.song_def->envelopes[env_id]); 627 | } 628 | break; 629 | case SET_GLIDE_SPEED: { 630 | uint8_t gl_low = pgm_read_byte(&(cur_pat[song_info.pat_idx++])); 631 | uint8_t gl_high = pgm_read_byte(&(cur_pat[song_info.pat_idx++])); 632 | setPortaRate(voice, gl_low | (gl_high << 8)); 633 | } 634 | break; 635 | case PORTAMENTO: { 636 | setPorta(voice, 1); 637 | } 638 | break; 639 | } 640 | } while (!done_row); 641 | } 642 | 643 | if (song_info.pat_idx >= pgm_read_word( 644 | &(song_info.song_def->pattern_lengths[song_info.cur_pattern]))) { 645 | song_info.pat_idx = 0; 646 | song_info.order_idx++; 647 | if (song_info.order_idx >= song_info.song_def->num_patterns) 648 | song_info.order_idx = 0; 649 | song_info.cur_pattern 650 | = pgm_read_byte(&song_info.song_def->pattern_order[song_info.order_idx]); 651 | } 652 | 653 | if (++song_info.tick >= song_info.next_tick) { 654 | song_info.tick = 0; 655 | } 656 | 657 | // Now lets do our envelopes and effects! 658 | for (uint8_t i = 0; i < N_VOICES; ++i) { 659 | struct TTVOICE* v = &voices[i]; 660 | if (v->gliding && v->hz != v->porta_pitch) { 661 | // add our fractional component back in. 662 | uint32_t fp_hz = (v->hz << 4) + v->f_hz; 663 | if (v->hz < v->porta_pitch) { // gliding up. 664 | uint32_t p_mul = 0x10000 + v->porta_rate; 665 | fp_hz = (fp_hz * p_mul) >> 16; 666 | v->hz = MIN(fp_hz >> 4, v->porta_pitch); 667 | } else { // Glide down. 668 | uint32_t p_mul = 0x10000 - v->porta_rate; 669 | fp_hz = (fp_hz * p_mul) >> 16; 670 | v->hz = MAX(fp_hz >> 4, v->porta_pitch); 671 | } 672 | v->f_hz = fp_hz & 0xf; 673 | v->_setPitch(v, v->hz); 674 | } 675 | if (v->enabled && voices[i]._envelope && v->_env_idx 676 | < v->_envelope->num_points) { 677 | if (v->_env_ticks == v->_envelope->sustain_tick) { 678 | v->sustaining = 1; 679 | } 680 | if (v->sustaining) 681 | continue; // Move along. We're sustaining! 682 | v->_env_ticks++; 683 | v->_env_volume += v->_envelope->env_slopes[v->_env_idx]; 684 | v->_env_volume = MAX(0, v->_env_volume); 685 | v->_env_volume = MIN(0xff << 7, v->_env_volume); 686 | v->_setVolume(v, v->volume); 687 | if (--v->_env_ticks_left_for_node == 0) { 688 | v->_env_idx++; 689 | if (v->_env_idx == v->_envelope->num_points) 690 | continue; 691 | v->_env_ticks_left_for_node = v->_envelope->point_ticks[v->_env_idx]; 692 | } 693 | } else { 694 | if (v->enabled && voices[i]._envelope) { 695 | if (v->_s_volume < 5) 696 | v->enabled = 0; 697 | } 698 | } 699 | } 700 | } 701 | -------------------------------------------------------------------------------- /tinytune/tinytune.h: -------------------------------------------------------------------------------- 1 | #ifndef __TINYTUNE__ 2 | #define __TINYTUNE__ 3 | 4 | // Code definitions for song events 5 | #define NOTE_ON 0 6 | #define NOTE_OFF 1 7 | #define ROW_ADV 3 8 | #define SET_VOL 4 9 | #define SET_ENV 5 10 | #define SET_GLIDE_SPEED 6 11 | #define PORTAMENTO 7 12 | #define NOTE_ON_FULL_VOL 8 13 | 14 | #include 15 | #include 16 | 17 | typedef char bool; 18 | typedef uint8_t* PROGMEM songdata; 19 | 20 | // forward declarations 21 | struct TTENVELOPE; 22 | struct song_definition; 23 | 24 | // Initializes the system. This must be called before any synthesis can occur. 25 | // It uses the PWM registers and Timer1 26 | // Output is on PIN 3 (OC1B) 27 | void initTinyTune(void); 28 | 29 | // Voices are indexed from 0 up to N_VOICES 30 | // Sets the voice to PWM audio output 31 | void initVoicePWM(uint8_t voice); 32 | 33 | // Sets the voice to triangle/saw audio output 34 | void initVoiceTRI(uint8_t voice); 35 | 36 | // Sets the voice to noise output 37 | void initVoiceNOISE(uint8_t voice); 38 | 39 | // The following voice modifiers can be called at any time. 40 | // Sets the volume on a voice. 41 | void setVolume(uint8_t voice, uint8_t volume); 42 | 43 | /* Sets the Duty cycle on a voice. For PWM this implies the ratio of high/low, as 44 | a ratio of duty/255. For triangle waves, values between 0 - 255 45 | transition between TRI and SAW waves. 46 | */ 47 | void setDuty(uint8_t voice, uint8_t duty); 48 | 49 | // Sets the pitch of a voice in Hz 50 | void setPitch(uint8_t voice, uint16_t pitch); 51 | 52 | // Enables/disables a voice. This must be set to true to hear anything for a voice. 53 | void setEnable(uint8_t voice, bool enable); 54 | 55 | /* Sets the bit-wise quantization for a voice. E.g, crunch=3, 56 | would quantize the voice to 8 - 3 = 5-bits. 57 | crunch=0 disables this. 58 | Sounds most distinctive on TRI waves. Compare crunch=4 to the TRI channel on an NES. 59 | */ 60 | void setBitCrunch(uint8_t voice, uint8_t crunch); 61 | 62 | 63 | // The following only function during song playback 64 | 65 | // Sets the envelope to use during song playback on this channel. 66 | void setEnvelope(uint8_t voice, const struct TTENVELOPE* envelope); 67 | 68 | /* Sets the portamento rate on this voice. p_rate is a 12.4 fixed point frequency 69 | multiplier applied per song tick. 70 | */ 71 | void setPortaRate(uint8_t voice, uint16_t p_rate); 72 | // Enables or disables pitch portamento on this voice. 73 | void setPorta(uint8_t voice, bool enable); 74 | 75 | // Waits for ms milliseconds, using the sample clock. 76 | void waitMS(uint16_t ms); 77 | 78 | // Plays back a song, in the background defined by song_def 79 | void playSong(struct song_definition* song_def); 80 | 81 | 82 | // Internal data structures. 83 | 84 | struct TTENVELOPE { 85 | int8_t num_points; 86 | // Fixed point 9.7 starting volume; 87 | uint8_t starting_level; 88 | // Fixed point slopes 9.7 89 | int16_t* env_slopes; 90 | // Number of ticks to run each slope. 91 | uint8_t* point_ticks; 92 | // Tick to halt envelope until note off. 93 | uint8_t sustain_tick; 94 | }; 95 | 96 | struct TTVOICE { 97 | enum { 98 | TT_PWM, TT_TRI, TT_NOISE 99 | } voice_type; 100 | 101 | // Integer hz. 102 | uint16_t hz; 103 | // Fractional hz for portamento accuracy. 0.4 fp. 104 | uint8_t f_hz; 105 | // Volume. 106 | uint8_t volume; 107 | int8_t _s_volume; 108 | // Fixed 9.7 envelope volume 109 | int16_t _env_volume; 110 | // Index of the current envelope's progress 111 | uint8_t _env_idx; 112 | // Number of ticks remaining on the current envelope node. 113 | uint8_t _env_ticks_left_for_node; 114 | //total env_ticks 115 | uint8_t _env_ticks; 116 | // Have we hit the sustain_pt? 117 | uint8_t sustaining; 118 | uint8_t gliding; 119 | uint16_t porta_rate; 120 | uint16_t porta_pitch; 121 | 122 | const struct TTENVELOPE* _envelope; 123 | // Voice enabled 124 | bool enabled; 125 | uint16_t _period; 126 | // error term. 127 | uint16_t _err; 128 | uint16_t bcrunch; 129 | void (*_getSample)(struct TTVOICE*); 130 | void (*_setVolume)(struct TTVOICE*, uint8_t); 131 | void (*_setDuty)(struct TTVOICE*, uint8_t duty); 132 | void (*_setPitch)(struct TTVOICE*, uint16_t pitch); 133 | void (*_setEnable)(struct TTVOICE*, bool enable); 134 | union { 135 | struct { 136 | uint8_t duty; 137 | uint16_t duty_period; 138 | } pwm; 139 | struct { 140 | uint8_t duty; 141 | uint16_t rise_period; 142 | // 9.7 bit fixed point. 143 | int16_t fp_vol; 144 | int16_t level; 145 | int16_t rise_slp; 146 | int16_t fall_slp; 147 | } tri; 148 | } _params; 149 | }; 150 | 151 | struct song_definition { 152 | const uint8_t* const * pattern_data; 153 | const uint16_t* pattern_lengths; 154 | uint8_t num_patterns; 155 | const uint8_t* pattern_order; 156 | uint16_t bpm; 157 | uint8_t rows_per_beat; 158 | uint8_t ticks_per_row; 159 | const struct TTENVELOPE** envelopes; 160 | }; 161 | 162 | struct song_info { 163 | uint16_t samples_per_tick; 164 | uint16_t tick; 165 | uint16_t next_tick; 166 | uint16_t tick_smp_count; 167 | struct song_definition* song_def; 168 | uint8_t cur_pattern; 169 | uint8_t order_idx; 170 | uint16_t pat_idx; 171 | bool playing; 172 | } song_info; 173 | 174 | #endif 175 | -------------------------------------------------------------------------------- /tinytune_test/Makefile: -------------------------------------------------------------------------------- 1 | # Hey Emacs, this is a -*- makefile -*- 2 | 3 | # AVR-GCC Makefile template, derived from the WinAVR template (which 4 | # is public domain), believed to be neutral to any flavor of "make" 5 | # (GNU make, BSD make, SysV make) 6 | 7 | include songgen.mk 8 | 9 | AVRDUDE_PROGRAMMER = avrisp2 10 | AVRDUDE_PORT = usb 11 | 12 | MCU = attiny85 13 | FORMAT = ihex 14 | TARGET = tinytune_test 15 | TINYTUNE = ../tinytune/tinytune.c 16 | SRC = $(TARGET).c $(SONGS) $(TINYTUNE) 17 | ASRC = 18 | OPT = s 19 | 20 | # Name of this Makefile (used for "make depend"). 21 | MAKEFILE = Makefile 22 | 23 | # Debugging format. 24 | # Native formats for AVR-GCC's -g are stabs [default], or dwarf-2. 25 | # AVR (extended) COFF requires stabs, plus an avr-objcopy run. 26 | DEBUG = stabs 27 | 28 | # Compiler flag to set the C Standard level. 29 | # c89 - "ANSI" C 30 | # gnu89 - c89 plus GCC extensions 31 | # c99 - ISO C99 standard (not yet fully implemented) 32 | # gnu99 - c99 plus GCC extensions 33 | CSTANDARD = -std=gnu99 34 | 35 | # Place -D or -U options here 36 | CDEFS = 37 | 38 | # Place -I options here 39 | CINCS = -I.. 40 | 41 | 42 | CDEBUG = -g$(DEBUG) 43 | CWARN = -Wall -Wstrict-prototypes 44 | CTUNING = -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -ffunction-sections -fdata-sections -fno-tree-scev-cprop 45 | #CEXTRA = -Wa,-adhlns=$(<:.c=.lst) 46 | CFLAGS = $(CDEBUG) $(CDEFS) $(CINCS) -O$(OPT) $(CWARN) $(CSTANDARD) $(CEXTRA) $(CTUNING) 47 | 48 | #ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs 49 | 50 | 51 | #Additional libraries. 52 | 53 | # Minimalistic printf version 54 | PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min 55 | 56 | # Floating point printf version (requires MATH_LIB = -lm below) 57 | PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt 58 | 59 | PRINTF_LIB = 60 | 61 | # Minimalistic scanf version 62 | SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min 63 | 64 | # Floating point + %[ scanf version (requires MATH_LIB = -lm below) 65 | SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt 66 | 67 | SCANF_LIB = 68 | 69 | MATH_LIB = -lm 70 | 71 | # External memory options 72 | 73 | # 64 KB of external RAM, starting after internal RAM (ATmega128!), 74 | # used for variables (.data/.bss) and heap (malloc()). 75 | #EXTMEMOPTS = -Wl,--section-start,.data=0x801100,--defsym=__heap_end=0x80ffff 76 | 77 | # 64 KB of external RAM, starting after internal RAM (ATmega128!), 78 | # only used for heap (malloc()). 79 | #EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff 80 | 81 | EXTMEMOPTS = 82 | 83 | LDMAP = -Wl,-Map=$(TARGET).map,--cref 84 | LDFLAGS = $(EXTMEMOPTS) $(LDMAP) $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB) -Wl,--gc-sections 85 | 86 | 87 | # Programming support using avrdude. Settings and variables. 88 | AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex 89 | AVRDUDE_WRITE_FUSES = -U lfuse:w:0xd1:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m 90 | #AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep 91 | 92 | 93 | # Uncomment the following if you want avrdude's erase cycle counter. 94 | # Note that this counter needs to be initialized first using -Yn, 95 | # see avrdude manual. 96 | #AVRDUDE_ERASE_COUNTER = -y 97 | 98 | # Uncomment the following if you do /not/ wish a verification to be 99 | # performed after programming the device. 100 | #AVRDUDE_NO_VERIFY = -V 101 | 102 | # Increase verbosity level. Please use this when submitting bug 103 | # reports about avrdude. See 104 | # to submit bug reports. 105 | #AVRDUDE_VERBOSE = -v -v 106 | 107 | AVRDUDE_BASIC = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) 108 | AVRDUDE_FLAGS = $(AVRDUDE_BASIC) $(AVRDUDE_NO_VERIFY) $(AVRDUDE_VERBOSE) $(AVRDUDE_ERASE_COUNTER) 109 | 110 | 111 | CC = avr-gcc 112 | OBJCOPY = avr-objcopy 113 | OBJDUMP = avr-objdump 114 | SIZE = avr-size 115 | NM = avr-nm 116 | AVRDUDE = avrdude 117 | REMOVE = rm -f 118 | RMDIR = rm -rf 119 | MKDIR = mkdir -p 120 | MV = mv -f 121 | 122 | # Define all object files. 123 | OBJ = $(SRC:.c=.o) $(ASRC:.S=.o) 124 | 125 | # Define all listing files. 126 | LST = $(ASRC:.S=.lst) $(SRC:.c=.lst) 127 | 128 | # Combine all necessary flags and optional flags. 129 | # Add target processor to flags. 130 | ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) 131 | ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) 132 | 133 | 134 | # Default target. 135 | all: build 136 | 137 | build: elf hex eep 138 | 139 | elf: $(TARGET).elf 140 | hex: $(TARGET).hex 141 | eep: $(TARGET).eep 142 | lss: $(TARGET).lss 143 | sym: $(TARGET).sym 144 | 145 | 146 | # Program the device. 147 | program: $(TARGET).hex $(TARGET).eep 148 | $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM) 149 | 150 | 151 | 152 | 153 | # Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB. 154 | COFFCONVERT=$(OBJCOPY) --debugging \ 155 | --change-section-address .data-0x800000 \ 156 | --change-section-address .bss-0x800000 \ 157 | --change-section-address .noinit-0x800000 \ 158 | --change-section-address .eeprom-0x810000 159 | 160 | 161 | coff: $(TARGET).elf 162 | $(COFFCONVERT) -O coff-avr $(TARGET).elf $(TARGET).cof 163 | 164 | 165 | extcoff: $(TARGET).elf 166 | $(COFFCONVERT) -O coff-ext-avr $(TARGET).elf $(TARGET).cof 167 | 168 | 169 | .SUFFIXES: .elf .hex .eep .lss .sym 170 | 171 | .elf.hex: 172 | $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ 173 | 174 | .elf.eep: 175 | -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \ 176 | --change-section-lma .eeprom=0 -O $(FORMAT) $< $@ 177 | 178 | # Create extended listing file from ELF output file. 179 | .elf.lss: 180 | $(OBJDUMP) -h -S $< > $@ 181 | 182 | # Create a symbol table from ELF output file. 183 | .elf.sym: 184 | $(NM) -n $< > $@ 185 | 186 | 187 | 188 | # Link: create ELF output file from object files. 189 | $(TARGET).elf: $(OBJ) 190 | $(CC) $(ALL_CFLAGS) $(OBJ) --output $@ $(LDFLAGS) 191 | 192 | 193 | # Compile: create object files from C source files. 194 | .c.o: 195 | $(CC) -c $(ALL_CFLAGS) $< -o $@ 196 | 197 | 198 | # Compile: create assembler files from C source files. 199 | .c.s: 200 | $(CC) -S $(ALL_CFLAGS) $< -o $@ 201 | 202 | 203 | # Assemble: create object files from assembler source files. 204 | .S.o: 205 | $(CC) -c $(ALL_ASFLAGS) $< -o $@ 206 | 207 | 208 | 209 | # Target: clean project. 210 | clean: 211 | $(RMDIR) test_songs; \ 212 | $(REMOVE) $(TARGET).hex $(TARGET).eep $(TARGET).cof $(TARGET).elf songgen.mk \ 213 | $(TARGET).map $(TARGET).sym $(TARGET).lss \ 214 | $(OBJ) $(LST) $(SRC:.c=.s) $(SRC:.c=.d) 215 | 216 | depend: 217 | if grep '^# DO NOT DELETE' $(MAKEFILE) >/dev/null; \ 218 | then \ 219 | sed -e '/^# DO NOT DELETE/,$$d' $(MAKEFILE) > \ 220 | $(MAKEFILE).$$$$ && \ 221 | $(MV) $(MAKEFILE).$$$$ $(MAKEFILE); \ 222 | fi 223 | echo '# DO NOT DELETE THIS LINE -- make depend depends on it.' \ 224 | >> $(MAKEFILE); \ 225 | $(CC) -M -mmcu=$(MCU) $(CDEFS) $(CINCS) $(SRC) $(ASRC) >> $(MAKEFILE) 226 | 227 | .PHONY: all build elf hex eep lss sym program coff extcoff clean depend 228 | 229 | XRNS2TT = python ../../utils/xrns2tt.py 230 | RNS_SONGS = $(wildcard ../test_songs/*.xrns) 231 | songgen.mk: $(RNS_SONGS) 232 | $(MKDIR) test_songs; \ 233 | cd test_songs; \ 234 | $(XRNS2TT) ../../test_songs/*.xrns; \ 235 | cd ..; \ 236 | echo "SONGS=`echo test_songs/*.c`" > songgen.mk 237 | 238 | program_fuses: 239 | $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FUSES) 240 | 241 | -------------------------------------------------------------------------------- /tinytune_test/tinytune_test.atsln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Atmel Studio Solution File, Format Version 11.00 4 | Project("{54F91283-7BC4-4236-8FF9-10F437C3AD48}") = "tinytune_test", "tinytune_test.cproj", "{EEEE1506-C34B-4ACC-B7BA-A42D13451428}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|AVR = Debug|AVR 9 | Release|AVR = Release|AVR 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {EEEE1506-C34B-4ACC-B7BA-A42D13451428}.Debug|AVR.ActiveCfg = Debug|AVR 13 | {EEEE1506-C34B-4ACC-B7BA-A42D13451428}.Debug|AVR.Build.0 = Debug|AVR 14 | {EEEE1506-C34B-4ACC-B7BA-A42D13451428}.Release|AVR.ActiveCfg = Release|AVR 15 | {EEEE1506-C34B-4ACC-B7BA-A42D13451428}.Release|AVR.Build.0 = Release|AVR 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /tinytune_test/tinytune_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Created: 1/15/2012 11:34:34 PM 3 | * Author: Blake Livingston 4 | */ 5 | 6 | #include 7 | #include 8 | #include "tinytune/tinytune.h" 9 | 10 | #define TEST_ZELDA 11 | 12 | #ifdef TEST_ZELDA 13 | #include "test_songs/zelda.h" 14 | int main(void) 15 | { 16 | initTinyTune(); 17 | initVoiceTRI(0); 18 | setBitCrunch(0,4); 19 | playSong(&zelda); 20 | while(1) 21 | { 22 | } 23 | } 24 | #endif 25 | 26 | #ifdef TEST_FAXANADU 27 | #include "test_songs/faxanadu.h" 28 | int main(void) 29 | { 30 | initTinyTune(); 31 | initVoiceTRI(2); 32 | setDuty(0,0x80); 33 | setDuty(0,0x85); 34 | setBitCrunch(2,4); 35 | playSong(&faxanadu); 36 | while(1) 37 | { 38 | } 39 | } 40 | #endif 41 | 42 | #ifdef TEST_PITCHBEND 43 | #include "test_songs/pitch.h" 44 | int main(void) 45 | { 46 | initTinyTune(); 47 | playSong(&pitch); 48 | while(1) 49 | { 50 | } 51 | } 52 | #endif 53 | 54 | #ifdef TEST_ENV 55 | #include "test_songs/env_test.h" 56 | int main(void) 57 | { 58 | initTinyTune(); 59 | playSong(&env_test); 60 | while(1) 61 | { 62 | } 63 | } 64 | #endif 65 | 66 | #ifdef TEST_DEMO1 67 | #include "test_songs/demo1.h" 68 | int main(void) 69 | { 70 | initTinyTune(); 71 | initVoiceTRI(4); 72 | initVoiceTRI(0); 73 | setDuty(1,0xe0); 74 | setDuty(2,0xe0); 75 | setBitCrunch(0,4); 76 | playSong(&demo1); 77 | while(1) 78 | { 79 | } 80 | } 81 | #endif 82 | 83 | #ifdef TEST_NOISE 84 | #include "test_songs/noise.h" 85 | int main(void) { 86 | initTinyTune(); 87 | initVoiceNOISE(0); 88 | initVoiceNOISE(1); 89 | playSong(&noise); 90 | while (1) { 91 | } 92 | } 93 | #endif 94 | -------------------------------------------------------------------------------- /tinytune_test/tinytune_test.cproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 2.0 5 | 6.2 6 | com.Atmel.AVRGCC8.C 7 | {eeee1506-c34b-4acc-b7ba-a42d13451428} 8 | ATtiny85 9 | none 10 | Executable 11 | C 12 | $(MSBuildProjectName) 13 | .elf 14 | $(MSBuildProjectDirectory)\$(Configuration) 15 | tinytune_test 16 | tinytune_test 17 | tinytune_test 18 | Native 19 | true 20 | false 21 | true 22 | true 23 | 0x20000000 24 | 25 | exception_table 26 | 2 27 | 0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | com.atmel.avrdbg.tool.ispmk2 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | com.atmel.avrdbg.tool.simulator 49 | 50 | 51 | Simulator 52 | 53 | 54 | 55 | 44000 56 | False 57 | True 58 | False 59 | True 60 | True 61 | False 62 | False 63 | 1000 64 | False 65 | False 66 | False 67 | False 68 | True 69 | False 70 | False 71 | True 72 | False 73 | False 74 | 75 | ISP 76 | 77 | 78 | 79 | 125000 80 | 81 | ISP 82 | 83 | com.atmel.avrdbg.tool.ispmk2 84 | 0000B0017710 85 | AVRISP mkII 86 | 87 | 88 | 89 | 90 | 91 | True 92 | True 93 | True 94 | True 95 | False 96 | True 97 | True 98 | 99 | 100 | NDEBUG 101 | 102 | 103 | 104 | 105 | ../.. 106 | 107 | 108 | Optimize for size (-Os) 109 | True 110 | True 111 | True 112 | 113 | 114 | libm 115 | 116 | 117 | 118 | 119 | cd .. 120 | 121 | mkdir test_songs 122 | cd test_songs 123 | 124 | cmd /c "python ../../utils/xrns2tt.py ../../test_songs/*.xrns" 125 | 126 | 127 | 128 | 129 | True 130 | True 131 | True 132 | True 133 | False 134 | True 135 | True 136 | 137 | 138 | ../.. 139 | 140 | 141 | True 142 | True 143 | True 144 | 145 | 146 | libm 147 | 148 | 149 | 150 | 151 | DEBUG 152 | 153 | 154 | Optimize (-O1) 155 | Default (-g2) 156 | Default (-Wa,-g) 157 | 158 | 159 | cd .. 160 | 161 | mkdir test_songs 162 | cd test_songs 163 | 164 | cmd /c "python ../../utils/xrns2tt.py ../../test_songs/*.xrns" 165 | 166 | 167 | 168 | compile 169 | settings.h 170 | 171 | 172 | compile 173 | tinytune.c 174 | 175 | 176 | compile 177 | tinytune.h 178 | 179 | 180 | compile 181 | demo1.c 182 | 183 | 184 | compile 185 | demo1.h 186 | 187 | 188 | compile 189 | env_test.c 190 | 191 | 192 | compile 193 | env_test.h 194 | 195 | 196 | compile 197 | faxanadu.c 198 | 199 | 200 | compile 201 | faxanadu.h 202 | 203 | 204 | compile 205 | noise.c 206 | 207 | 208 | compile 209 | noise.h 210 | 211 | 212 | compile 213 | pitch.c 214 | 215 | 216 | compile 217 | pitch.h 218 | 219 | 220 | compile 221 | zelda.c 222 | 223 | 224 | compile 225 | zelda.h 226 | 227 | 228 | compile 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /tinytune_test_atmega8/Makefile: -------------------------------------------------------------------------------- 1 | # Hey Emacs, this is a -*- makefile -*- 2 | 3 | # AVR-GCC Makefile template, derived from the WinAVR template (which 4 | # is public domain), believed to be neutral to any flavor of "make" 5 | # (GNU make, BSD make, SysV make) 6 | 7 | include songgen.mk 8 | 9 | AVRDUDE_PROGRAMMER = avrisp2 10 | AVRDUDE_PORT = usb 11 | 12 | MCU = atmega8 13 | FORMAT = ihex 14 | TARGET = tinytune_test 15 | TINYTUNE = ../tinytune/tinytune.c 16 | SRC = $(TARGET).c $(SONGS) $(TINYTUNE) 17 | ASRC = 18 | OPT = s 19 | 20 | # Name of this Makefile (used for "make depend"). 21 | MAKEFILE = Makefile 22 | 23 | # Debugging format. 24 | # Native formats for AVR-GCC's -g are stabs [default], or dwarf-2. 25 | # AVR (extended) COFF requires stabs, plus an avr-objcopy run. 26 | DEBUG = stabs 27 | 28 | # Compiler flag to set the C Standard level. 29 | # c89 - "ANSI" C 30 | # gnu89 - c89 plus GCC extensions 31 | # c99 - ISO C99 standard (not yet fully implemented) 32 | # gnu99 - c99 plus GCC extensions 33 | CSTANDARD = -std=gnu99 34 | 35 | # Place -D or -U options here 36 | CDEFS = 37 | 38 | # Place -I options here 39 | CINCS = -I.. 40 | 41 | 42 | CDEBUG = -g$(DEBUG) 43 | CWARN = -Wall -Wstrict-prototypes 44 | CTUNING = -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -ffunction-sections -fdata-sections -fno-tree-scev-cprop 45 | #CEXTRA = -Wa,-adhlns=$(<:.c=.lst) 46 | CFLAGS = $(CDEBUG) $(CDEFS) $(CINCS) -O$(OPT) $(CWARN) $(CSTANDARD) $(CEXTRA) $(CTUNING) 47 | 48 | #ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs 49 | 50 | 51 | #Additional libraries. 52 | 53 | # Minimalistic printf version 54 | PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min 55 | 56 | # Floating point printf version (requires MATH_LIB = -lm below) 57 | PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt 58 | 59 | PRINTF_LIB = 60 | 61 | # Minimalistic scanf version 62 | SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min 63 | 64 | # Floating point + %[ scanf version (requires MATH_LIB = -lm below) 65 | SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt 66 | 67 | SCANF_LIB = 68 | 69 | MATH_LIB = -lm 70 | 71 | # External memory options 72 | 73 | # 64 KB of external RAM, starting after internal RAM (ATmega128!), 74 | # used for variables (.data/.bss) and heap (malloc()). 75 | #EXTMEMOPTS = -Wl,--section-start,.data=0x801100,--defsym=__heap_end=0x80ffff 76 | 77 | # 64 KB of external RAM, starting after internal RAM (ATmega128!), 78 | # only used for heap (malloc()). 79 | #EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff 80 | 81 | EXTMEMOPTS = 82 | 83 | LDMAP = -Wl,-Map=$(TARGET).map,--cref 84 | LDFLAGS = $(EXTMEMOPTS) $(LDMAP) $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB) -Wl,--gc-sections 85 | 86 | 87 | # Programming support using avrdude. Settings and variables. 88 | AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex 89 | AVRDUDE_WRITE_FUSES = -U lfuse:w:0xe4:m -U hfuse:w:0xd9:m 90 | #AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep 91 | 92 | 93 | # Uncomment the following if you want avrdude's erase cycle counter. 94 | # Note that this counter needs to be initialized first using -Yn, 95 | # see avrdude manual. 96 | #AVRDUDE_ERASE_COUNTER = -y 97 | 98 | # Uncomment the following if you do /not/ wish a verification to be 99 | # performed after programming the device. 100 | #AVRDUDE_NO_VERIFY = -V 101 | 102 | # Increase verbosity level. Please use this when submitting bug 103 | # reports about avrdude. See 104 | # to submit bug reports. 105 | #AVRDUDE_VERBOSE = -v -v 106 | 107 | -AVRDUDE_BASIC = -p $(MCU) -B 4 -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) 108 | -AVRDUDE_FLAGS = $(AVRDUDE_BASIC) $(AVRDUDE_NO_VERIFY) $(AVRDUDE_VERBOSE) $(AVRDUDE_ERASE_COUNTER) -B 4 109 | 110 | CC = avr-gcc 111 | OBJCOPY = avr-objcopy 112 | OBJDUMP = avr-objdump 113 | SIZE = avr-size 114 | NM = avr-nm 115 | AVRDUDE = avrdude 116 | REMOVE = rm -f 117 | RMDIR = rm -rf 118 | MKDIR = mkdir -p 119 | MV = mv -f 120 | 121 | # Define all object files. 122 | OBJ = $(SRC:.c=.o) $(ASRC:.S=.o) 123 | 124 | # Define all listing files. 125 | LST = $(ASRC:.S=.lst) $(SRC:.c=.lst) 126 | 127 | # Combine all necessary flags and optional flags. 128 | # Add target processor to flags. 129 | ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) 130 | ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) 131 | 132 | 133 | # Default target. 134 | all: build 135 | 136 | build: elf hex eep 137 | 138 | elf: $(TARGET).elf 139 | hex: $(TARGET).hex 140 | eep: $(TARGET).eep 141 | lss: $(TARGET).lss 142 | sym: $(TARGET).sym 143 | 144 | 145 | # Program the device. 146 | program: $(TARGET).hex $(TARGET).eep 147 | $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM) 148 | 149 | 150 | 151 | 152 | # Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB. 153 | COFFCONVERT=$(OBJCOPY) --debugging \ 154 | --change-section-address .data-0x800000 \ 155 | --change-section-address .bss-0x800000 \ 156 | --change-section-address .noinit-0x800000 \ 157 | --change-section-address .eeprom-0x810000 158 | 159 | 160 | coff: $(TARGET).elf 161 | $(COFFCONVERT) -O coff-avr $(TARGET).elf $(TARGET).cof 162 | 163 | 164 | extcoff: $(TARGET).elf 165 | $(COFFCONVERT) -O coff-ext-avr $(TARGET).elf $(TARGET).cof 166 | 167 | 168 | .SUFFIXES: .elf .hex .eep .lss .sym 169 | 170 | .elf.hex: 171 | $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ 172 | 173 | .elf.eep: 174 | -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \ 175 | --change-section-lma .eeprom=0 -O $(FORMAT) $< $@ 176 | 177 | # Create extended listing file from ELF output file. 178 | .elf.lss: 179 | $(OBJDUMP) -h -S $< > $@ 180 | 181 | # Create a symbol table from ELF output file. 182 | .elf.sym: 183 | $(NM) -n $< > $@ 184 | 185 | 186 | 187 | # Link: create ELF output file from object files. 188 | $(TARGET).elf: $(OBJ) 189 | $(CC) $(ALL_CFLAGS) $(OBJ) --output $@ $(LDFLAGS) 190 | 191 | 192 | # Compile: create object files from C source files. 193 | .c.o: 194 | $(CC) -c $(ALL_CFLAGS) $< -o $@ 195 | 196 | 197 | # Compile: create assembler files from C source files. 198 | .c.s: 199 | $(CC) -S $(ALL_CFLAGS) $< -o $@ 200 | 201 | 202 | # Assemble: create object files from assembler source files. 203 | .S.o: 204 | $(CC) -c $(ALL_ASFLAGS) $< -o $@ 205 | 206 | 207 | 208 | # Target: clean project. 209 | clean: 210 | $(RMDIR) test_songs; \ 211 | $(REMOVE) $(TARGET).hex $(TARGET).eep $(TARGET).cof $(TARGET).elf songgen.mk \ 212 | $(TARGET).map $(TARGET).sym $(TARGET).lss \ 213 | $(OBJ) $(LST) $(SRC:.c=.s) $(SRC:.c=.d) 214 | 215 | depend: 216 | if grep '^# DO NOT DELETE' $(MAKEFILE) >/dev/null; \ 217 | then \ 218 | sed -e '/^# DO NOT DELETE/,$$d' $(MAKEFILE) > \ 219 | $(MAKEFILE).$$$$ && \ 220 | $(MV) $(MAKEFILE).$$$$ $(MAKEFILE); \ 221 | fi 222 | echo '# DO NOT DELETE THIS LINE -- make depend depends on it.' \ 223 | >> $(MAKEFILE); \ 224 | $(CC) -M -mmcu=$(MCU) $(CDEFS) $(CINCS) $(SRC) $(ASRC) >> $(MAKEFILE) 225 | 226 | .PHONY: all build elf hex eep lss sym program coff extcoff clean depend 227 | 228 | XRNS2TT = python ../../utils/xrns2tt.py 229 | RNS_SONGS = $(wildcard ../test_songs/*.xrns) 230 | songgen.mk: $(RNS_SONGS) 231 | $(MKDIR) test_songs; \ 232 | cd test_songs; \ 233 | $(XRNS2TT) ../../test_songs/*.xrns; \ 234 | cd ..; \ 235 | echo "SONGS=`echo test_songs/*.c`" > songgen.mk 236 | 237 | program_fuses: 238 | $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FUSES) 239 | 240 | -------------------------------------------------------------------------------- /tinytune_test_atmega8/tinytune_test.atsln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Atmel Studio Solution File, Format Version 11.00 4 | Project("{54F91283-7BC4-4236-8FF9-10F437C3AD48}") = "tinytune_test", "tinytune_test.cproj", "{EEEE1506-C34B-4ACC-B7BA-A42D13451428}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|AVR = Debug|AVR 9 | Release|AVR = Release|AVR 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {EEEE1506-C34B-4ACC-B7BA-A42D13451428}.Debug|AVR.ActiveCfg = Debug|AVR 13 | {EEEE1506-C34B-4ACC-B7BA-A42D13451428}.Debug|AVR.Build.0 = Debug|AVR 14 | {EEEE1506-C34B-4ACC-B7BA-A42D13451428}.Release|AVR.ActiveCfg = Release|AVR 15 | {EEEE1506-C34B-4ACC-B7BA-A42D13451428}.Release|AVR.Build.0 = Release|AVR 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /tinytune_test_atmega8/tinytune_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Created: 1/15/2012 11:34:34 PM 3 | * Author: Blake Livingston 4 | */ 5 | 6 | #include 7 | #include 8 | #include "tinytune/tinytune.h" 9 | 10 | #define TEST_ZELDA 11 | 12 | #ifdef TEST_ZELDA 13 | #include "test_songs/zelda.h" 14 | int main(void) 15 | { 16 | initTinyTune(); 17 | initVoiceTRI(0); 18 | setBitCrunch(0,4); 19 | playSong(&zelda); 20 | while(1) 21 | { 22 | } 23 | } 24 | #endif 25 | 26 | #ifdef TEST_FAXANADU 27 | #include "test_songs/faxanadu.h" 28 | int main(void) 29 | { 30 | initTinyTune(); 31 | initVoiceTRI(2); 32 | setDuty(0,0x80); 33 | setDuty(0,0x85); 34 | setBitCrunch(2,4); 35 | playSong(&faxanadu); 36 | while(1) 37 | { 38 | } 39 | } 40 | #endif 41 | 42 | #ifdef TEST_PITCHBEND 43 | #include "test_songs/pitch.h" 44 | int main(void) 45 | { 46 | initTinyTune(); 47 | playSong(&pitch); 48 | while(1) 49 | { 50 | } 51 | } 52 | #endif 53 | 54 | #ifdef TEST_ENV 55 | #include "test_songs/env_test.h" 56 | int main(void) 57 | { 58 | initTinyTune(); 59 | playSong(&env_test); 60 | while(1) 61 | { 62 | } 63 | } 64 | #endif 65 | 66 | #ifdef TEST_DEMO1 67 | #include "test_songs/demo1.h" 68 | int main(void) 69 | { 70 | initTinyTune(); 71 | initVoiceTRI(4); 72 | initVoiceTRI(0); 73 | setDuty(1,0xe0); 74 | setDuty(2,0xe0); 75 | setBitCrunch(0,4); 76 | playSong(&demo1); 77 | while(1) 78 | { 79 | } 80 | } 81 | #endif 82 | 83 | #ifdef TEST_NOISE 84 | #include "test_songs/noise.h" 85 | int main(void) { 86 | initTinyTune(); 87 | initVoiceNOISE(0); 88 | initVoiceNOISE(1); 89 | playSong(&noise); 90 | while (1) { 91 | } 92 | } 93 | #endif 94 | -------------------------------------------------------------------------------- /tinytune_test_atmega8/tinytune_test.cproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 2.0 5 | 6.2 6 | com.Atmel.AVRGCC8.C 7 | {eeee1506-c34b-4acc-b7ba-a42d13451428} 8 | ATmega8 9 | none 10 | Executable 11 | C 12 | $(MSBuildProjectName) 13 | .elf 14 | $(MSBuildProjectDirectory)\$(Configuration) 15 | tinytune_test 16 | tinytune_test 17 | tinytune_test 18 | Native 19 | true 20 | false 21 | true 22 | true 23 | 0x20000000 24 | 25 | exception_table 26 | 2 27 | 0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | com.atmel.avrdbg.tool.ispmk2 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | com.atmel.avrdbg.tool.simulator 49 | 50 | 51 | Simulator 52 | 53 | 54 | 55 | 44000 56 | False 57 | True 58 | False 59 | True 60 | True 61 | False 62 | False 63 | 1000 64 | False 65 | False 66 | False 67 | False 68 | True 69 | False 70 | False 71 | True 72 | False 73 | False 74 | 75 | ISP 76 | 77 | 78 | 79 | 125000 80 | 81 | ISP 82 | 83 | com.atmel.avrdbg.tool.ispmk2 84 | 0000B0017710 85 | AVRISP mkII 86 | 87 | 88 | 89 | 90 | 91 | True 92 | True 93 | True 94 | True 95 | False 96 | True 97 | True 98 | 99 | 100 | NDEBUG 101 | 102 | 103 | 104 | 105 | ../.. 106 | 107 | 108 | Optimize for size (-Os) 109 | True 110 | True 111 | True 112 | 113 | 114 | libm 115 | 116 | 117 | 118 | 119 | cd .. 120 | 121 | mkdir test_songs 122 | cd test_songs 123 | 124 | cmd /c "python ../../utils/xrns2tt.py ../../test_songs/*.xrns" 125 | 126 | 127 | 128 | 129 | True 130 | True 131 | True 132 | True 133 | False 134 | True 135 | True 136 | 137 | 138 | ../.. 139 | 140 | 141 | True 142 | True 143 | True 144 | 145 | 146 | libm 147 | 148 | 149 | 150 | 151 | DEBUG 152 | 153 | 154 | Optimize (-O1) 155 | Default (-g2) 156 | Default (-Wa,-g) 157 | 158 | 159 | cd .. 160 | 161 | mkdir test_songs 162 | cd test_songs 163 | 164 | cmd /c "python ../../utils/xrns2tt.py ../../test_songs/*.xrns" 165 | 166 | 167 | 168 | compile 169 | settings.h 170 | 171 | 172 | compile 173 | tinytune.c 174 | 175 | 176 | compile 177 | tinytune.h 178 | 179 | 180 | compile 181 | 182 | 183 | compile 184 | demo1.c 185 | 186 | 187 | compile 188 | demo1.h 189 | 190 | 191 | compile 192 | env_test.c 193 | 194 | 195 | compile 196 | env_test.h 197 | 198 | 199 | compile 200 | faxanadu.c 201 | 202 | 203 | compile 204 | faxanadu.h 205 | 206 | 207 | compile 208 | noise.c 209 | 210 | 211 | compile 212 | noise.h 213 | 214 | 215 | compile 216 | pitch.c 217 | 218 | 219 | compile 220 | zelda.c 221 | 222 | 223 | compile 224 | zelda.h 225 | 226 | 227 | compile 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /utils/xrns2tt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from xml.etree import ElementTree 3 | import glob 4 | import optparse 5 | import os 6 | import sys 7 | import zipfile 8 | 9 | class Envelope(object): 10 | env_tpl = """ 11 | static int16_t slopes_%d[] = {%s}; 12 | static uint8_t pticks_%d[] = {%s}; 13 | static struct TTENVELOPE env_%d = { 14 | .num_points = %d, 15 | .starting_level = %d, 16 | .env_slopes = slopes_%d, 17 | .point_ticks = pticks_%d, 18 | .sustain_tick = %d 19 | }; 20 | """ 21 | 22 | def parse(self, env, tpl, lpb): 23 | def beats_to_songticks(b): 24 | return (b / 256.0) * tpl * lpb 25 | 26 | if env.find('IsActive/Value').text =="1.0": 27 | self.active = True 28 | else: 29 | self.active = False 30 | return 31 | if env.find('SustainIsActive').text == 'true': 32 | 33 | self.sustain = True 34 | self.sustain_pt = int(env.find('SustainPos').text) 35 | self.sustain_pt = int(beats_to_songticks(self.sustain_pt)) 36 | else: 37 | self.sustain = False 38 | self.sustain_pt = 255 39 | 40 | env_ticks = [] 41 | levels = [] 42 | for point in env.find('Nodes/Points'): 43 | tick, level = point.text.split(',') 44 | tick = int(tick) 45 | levels.append(float(level)) 46 | # Envelope ticks are NOT song ticks. 47 | # there are 24 env ticks per beat. 48 | env_ticks.append(int(beats_to_songticks(tick))) 49 | self.slopes = [0] 50 | if env_ticks[0] != 0: 51 | self.tick_deltas = [env_ticks[0]] 52 | self.slopes = [0] 53 | else: 54 | self.tick_deltas = [] 55 | self.slopes = [] 56 | self.start_level = int(levels[0] * 255) 57 | for i in range(1, len(levels)): 58 | d = levels[i] - levels[i - 1] 59 | t_d = env_ticks[i] - env_ticks[i - 1] 60 | # We're rolling 9.7 bit fixed point on the slopes. 61 | self.slopes.append((int(d * 255) << 7) / t_d) 62 | self.tick_deltas.append(t_d) 63 | 64 | def c_dump(self, env_idx): 65 | slopes = ", ".join((str(sl) for sl in self.slopes)) 66 | pticks = ", ".join((str(min(pt, 255)) for pt in self.tick_deltas)) 67 | dump = self.env_tpl % (env_idx, slopes, 68 | env_idx, pticks, 69 | env_idx, 70 | len(self.slopes), 71 | self.start_level, 72 | env_idx, env_idx, 73 | self.sustain_pt) 74 | return dump 75 | 76 | def xrns_to_tt(input_file, 77 | output_base, 78 | track_tunings): 79 | zf = zipfile.ZipFile(input_file) 80 | song = ElementTree.fromstring(zf.read('Song.xml')) 81 | zf.close() 82 | track_info = song.find('Tracks') 83 | t_idx = 0 84 | active_tracks = set() 85 | print "converting %s" % input_file 86 | bpm = int(song.find('*/BeatsPerMin').text) 87 | lpb = int(song.find('*/LinesPerBeat').text) 88 | tpl = int(song.find('*/TicksPerLine').text) 89 | 90 | for t in track_info: 91 | if t.find('State').text == "Active": 92 | active_tracks.add(t_idx) 93 | t_idx += 1 94 | 95 | xpatterns = song.find('PatternPool/Patterns') 96 | 97 | patterns = [] 98 | 99 | instruments = song.find('Instruments') 100 | envelopes = [] 101 | inst_remap = {} 102 | for inst_id, inst in enumerate(instruments): 103 | vol_env = inst.find('.//SampleEnvelopeModulationDevice') 104 | if vol_env is not None: 105 | env = Envelope() 106 | env.parse(vol_env, tpl, lpb) 107 | if env.active: 108 | inst_remap[inst_id] = len(envelopes) 109 | envelopes.append(env) 110 | 111 | total_notes = 0 112 | 113 | for xpat in xpatterns: 114 | nlines = xpat.find('NumberOfLines') 115 | if nlines is None: 116 | continue 117 | nlines = int(nlines.text) 118 | tracks = xpat.find('Tracks') 119 | 120 | notelines = [[None for t in xrange(len(tracks))] for l in xrange(nlines)] 121 | tracknum = 0 122 | for track in tracks: 123 | if not tracknum in active_tracks: 124 | tracknum += 1 125 | continue 126 | lines = track.find('Lines') 127 | if lines is None: 128 | tracknum += 1 129 | continue 130 | for line in lines: 131 | line_index = int(line.get('index')) 132 | nc = line.find('*/NoteColumn') 133 | fc = line.find('*/EffectColumn') 134 | if nc is not None or fc is not None: 135 | if line_index >= nlines:continue 136 | total_notes += 1 137 | notelines[line_index][tracknum] = (nc, fc) 138 | tracknum += 1 139 | patterns.append(notelines) 140 | 141 | sequence = [] 142 | seq = song.find('*/SequenceEntries') 143 | for se in seq: 144 | pat = int(se.find('Pattern').text) 145 | if pat < len(patterns): 146 | sequence.append(int(se.find('Pattern').text)) 147 | 148 | active_patterns = list(set(sequence)) 149 | active_patterns.sort() 150 | pattern_remap = dict((j, i) for i, j in enumerate(active_patterns)) 151 | 152 | sequence = [pattern_remap[s] for s in sequence] 153 | 154 | NOTE_ON = 0 155 | NOTE_OFF = 1 156 | ROW_ADV = 3 157 | SET_VOL = 4 158 | SET_ENV = 5 159 | SET_GLIDE_SPEED = 6 160 | PORTAMENTO = 7 161 | NOTE_ON_FULL_VOL = 8 162 | ttcode_patterns = [] 163 | 164 | def mkcode(voice, code): 165 | return ((voice & 0xf) << 4) + code 166 | 167 | nlist = {'C-' : 12, 168 | 'C#' : 13, 169 | 'D-' : 14, 170 | 'D#' : 15, 171 | 'E-' : 16, 172 | 'F-' : 17, 173 | 'F#' : 18, 174 | 'G-' : 19, 175 | 'G#' : 20, 176 | 'A-' : 21, 177 | 'A#' : 22, 178 | 'B-' : 23, 179 | 'B#' : 24} 180 | 181 | def note_to_num(note): 182 | n = note[0:2] 183 | octave = int(note[2]) 184 | return 12 * octave + nlist[n] 185 | 186 | def calcPitchGlideMult(glide_speed, tpb): 187 | # Pitch glides are in 1/16 semitone per line 188 | # Applied each tick. 189 | # We don't want to do this on the mcu, so we convert it 190 | # to fixed point here. 191 | return int(0x10000 * (2 ** (((glide_speed / 16.0) / 12.0) / tpb) - 1)) 192 | 193 | for p_idx in xrange(len(patterns)): 194 | if not p_idx in active_patterns: 195 | continue 196 | cur_pat = patterns[p_idx] 197 | tt_pat = [] 198 | last_note_line = 0 199 | # Instruments are set at the beginning of the pattern and on change. 200 | # If there's an envelope at least. 201 | active_instruments = {} 202 | track_volumes = {} 203 | for l_idx in xrange(len(cur_pat)): 204 | tt_line = [] 205 | cur_line = cur_pat[l_idx] 206 | fxval = 0 207 | for t_idx in xrange(len(cur_line)): 208 | cnote = cur_line[t_idx] 209 | if cnote is None or t_idx not in active_tracks: 210 | continue 211 | note, fx = cnote 212 | if fx is not None: 213 | fxcode = None 214 | for elem in fx: 215 | if elem.tag == "Number": 216 | fxcode = elem.text 217 | if elem.tag == "Value": 218 | fxval = int(elem.text, 16) 219 | if fxcode == "0G": 220 | if fxval != 0: 221 | tcode = mkcode(t_idx, SET_GLIDE_SPEED) 222 | tt_line.append(tcode) 223 | glide_mult = calcPitchGlideMult(fxval, tpl) 224 | tt_line.append(glide_mult & 0xff) 225 | tt_line.append(glide_mult >> 8) 226 | tcode = mkcode(t_idx, PORTAMENTO) 227 | tt_line.append(tcode) 228 | if note is None: 229 | continue 230 | note_val = None 231 | volume = None 232 | inst = None 233 | for elem in note: 234 | if elem.tag == 'Note': 235 | if elem.text == 'OFF': 236 | tcode = mkcode(t_idx, NOTE_OFF) 237 | tt_line.append(tcode) 238 | else: 239 | if elem.text != '---': 240 | note_val = note_to_num(elem.text) 241 | note_val += track_tunings.get(t_idx, 0) 242 | track_volumes[t_idx] = 0xff 243 | volume = 0xff 244 | if elem.tag == 'Instrument': 245 | if elem.text != '..': 246 | i = int(elem.text, 16) 247 | if active_instruments.get(t_idx, -1) != i and i in inst_remap: 248 | active_instruments[t_idx] = i 249 | inst = inst_remap[i] 250 | if elem.tag == 'Volume': 251 | if elem.text != '..': 252 | v = int(elem.text, 16) * 2 253 | else: 254 | v = 0xff 255 | 256 | if v is not None and track_volumes.get(t_idx, -1) != v: 257 | track_volumes[t_idx] = v 258 | volume = v 259 | else: 260 | volume = None 261 | # print "VOLv",volume, t_idx, v,track_volumes 262 | if (note_val is not None or 263 | volume is not None or 264 | inst is not None): 265 | if inst is not None: 266 | tcode = mkcode(t_idx, SET_ENV) 267 | tt_line.extend([tcode, inst]) 268 | if note_val is not None: 269 | tcode = mkcode(t_idx, NOTE_ON) 270 | if volume == 0xff: 271 | tcode = mkcode(t_idx, NOTE_ON_FULL_VOL) 272 | volume = None 273 | else: 274 | tcode = mkcode(t_idx, NOTE_ON) 275 | if volume is not None: 276 | note_val |= 0x80 277 | tt_line.extend([tcode, note_val]) 278 | tt_line.append(volume) 279 | volume = None 280 | else: 281 | tt_line.extend([tcode, note_val]) 282 | if volume is not None: 283 | if volume == 0: 284 | tcode = mkcode(t_idx, NOTE_OFF) 285 | tt_line.append(tcode) 286 | else: 287 | tcode = mkcode(t_idx, SET_VOL) 288 | tt_line.extend([tcode, volume]) 289 | if tt_line: 290 | # we have line data! 291 | l_advance = l_idx - last_note_line 292 | last_note_line = l_idx 293 | while l_advance: 294 | adv = (l_advance - 1) & 0xf 295 | # insert row advances until the gap is covered 296 | tt_pat.append(mkcode(adv, ROW_ADV)) 297 | l_advance -= adv + 1 298 | tt_pat.extend(tt_line) 299 | tt_pat.extend(tt_line) 300 | l_advance = 1 + (l_idx - last_note_line) 301 | 302 | while l_advance: 303 | adv = (l_advance - 1) & 0xf 304 | # insert row advances until the pattern is done. 305 | tt_pat.append(mkcode(adv, ROW_ADV)) 306 | l_advance -= adv + 1 307 | ttcode_patterns.append(tt_pat) 308 | 309 | c_modulename = output_base + ".c" 310 | h_modulename = output_base + ".h" 311 | 312 | song_name = os.path.basename(output_base) 313 | 314 | c_outfile = file(c_modulename, 'w+') 315 | h_outfile = file(h_modulename, 'w+') 316 | 317 | c_tmpl = """ 318 | #include "tinytune/tinytune.h" 319 | // Envelopes 320 | %s 321 | // Patterns 322 | %s 323 | 324 | static const uint8_t* const p_dat[] PROGMEM = {%s}; 325 | static const uint16_t p_len[] PROGMEM = {%s}; 326 | static const uint8_t p_ord[] PROGMEM = {%s}; 327 | static const struct TTENVELOPE* envs[] = {%s}; 328 | struct song_definition %s = { 329 | .pattern_data = p_dat, 330 | .num_patterns = %d, 331 | .pattern_lengths = p_len, 332 | .pattern_order = p_ord, 333 | .bpm = %s, 334 | .rows_per_beat = %s, 335 | .ticks_per_row = %s, 336 | .envelopes = envs, 337 | }; 338 | """ 339 | 340 | h_tmpl = """#ifndef __%s__ 341 | #include "tinytune/tinytune.h" 342 | extern struct song_definition %s; 343 | #endif 344 | """ 345 | 346 | pattern_output = [] 347 | pattern_lengths = [] 348 | for tt_pat in ttcode_patterns: 349 | tt_lines = [] 350 | pattern_lengths.append(len(tt_pat)) 351 | while tt_pat: 352 | tt_l = tt_pat[0:20] 353 | tt_pat = tt_pat[20:] 354 | tt_lines.append(', '.join(["%u" % d for d in tt_l])) 355 | pattern_output.append(tt_lines) 356 | 357 | pat_defs = "\n".join(["static const uint8_t pattern_%d[] PROGMEM = {\n%s\n};" % (pid, ',\n'.join(data)) 358 | for pid, data in enumerate(pattern_output)]) 359 | 360 | env_defs = "\n".join(e.c_dump(idx) for idx, e in enumerate(envelopes)) 361 | 362 | c_output = c_tmpl % (env_defs, pat_defs, ",\n".join( 363 | ["pattern_%d" % i for i in range(len(pattern_output))]), 364 | ", ".join([str(l) for l in pattern_lengths]), 365 | ", ".join([str(l) for l in sequence]), 366 | ", ".join(["&env_%d" % i for i in range(len(envelopes))]), 367 | song_name, 368 | len(sequence), 369 | bpm, 370 | lpb, 371 | tpl) 372 | 373 | c_outfile.write(c_output) 374 | c_outfile.close() 375 | 376 | h_output = h_tmpl % (song_name.upper(), song_name) 377 | h_outfile.write(h_output) 378 | h_outfile.close() 379 | 380 | if __name__ == "__main__": 381 | option_parser = optparse.OptionParser(usage="usage: %prog [options] input.xrns [input2.xrns input3.xsnr ...]\n") 382 | option_parser.add_option("--tuning_dict", dest="tuning", action="store", 383 | type="string", default=None, 384 | help="Per-channel tuning offset. E.g. " 385 | "{0: -4, 1: -4, 3: -37, 4: -21, 5: -21}") 386 | options, values = option_parser.parse_args(sys.argv) 387 | 388 | if len(values) < 2: 389 | print option_parser.print_usage() 390 | sys.exit() 391 | if options.tuning: 392 | track_tunings = eval(options.tuning) 393 | else: 394 | track_tunings = {} 395 | for input_file in values[1:]: 396 | for exp_file in glob.glob(input_file): 397 | output_base = os.path.basename(os.path.splitext(exp_file)[0]) 398 | xrns_to_tt(exp_file, output_base, track_tunings) 399 | --------------------------------------------------------------------------------