├── .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.
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 |
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 |
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 | 
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 | 
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 |
--------------------------------------------------------------------------------