├── tone.wav ├── images ├── WART.pdf ├── WART.png ├── WART2.pdf ├── WART2.png ├── IMG_0559.JPG ├── IMG_0560.JPG ├── IMG_0561.JPG ├── IMG_0562.JPG ├── IMG_0567.JPG ├── IMG_0573.JPG ├── WART.graffle ├── WART2.graffle ├── arduino1.PNG ├── arduino_a.PNG ├── python_cls1.PNG ├── python_cls2.PNG ├── audacity_export.PNG ├── audacity_export2.PNG └── teensy-32-usb-microcontroller-development-board.jpg ├── wart.ino ├── LICENSE ├── wart.py └── README.md /tone.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/tone.wav -------------------------------------------------------------------------------- /images/WART.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/WART.pdf -------------------------------------------------------------------------------- /images/WART.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/WART.png -------------------------------------------------------------------------------- /images/WART2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/WART2.pdf -------------------------------------------------------------------------------- /images/WART2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/WART2.png -------------------------------------------------------------------------------- /images/IMG_0559.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/IMG_0559.JPG -------------------------------------------------------------------------------- /images/IMG_0560.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/IMG_0560.JPG -------------------------------------------------------------------------------- /images/IMG_0561.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/IMG_0561.JPG -------------------------------------------------------------------------------- /images/IMG_0562.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/IMG_0562.JPG -------------------------------------------------------------------------------- /images/IMG_0567.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/IMG_0567.JPG -------------------------------------------------------------------------------- /images/IMG_0573.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/IMG_0573.JPG -------------------------------------------------------------------------------- /images/WART.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/WART.graffle -------------------------------------------------------------------------------- /images/WART2.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/WART2.graffle -------------------------------------------------------------------------------- /images/arduino1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/arduino1.PNG -------------------------------------------------------------------------------- /images/arduino_a.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/arduino_a.PNG -------------------------------------------------------------------------------- /images/python_cls1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/python_cls1.PNG -------------------------------------------------------------------------------- /images/python_cls2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/python_cls2.PNG -------------------------------------------------------------------------------- /images/audacity_export.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/audacity_export.PNG -------------------------------------------------------------------------------- /images/audacity_export2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/audacity_export2.PNG -------------------------------------------------------------------------------- /images/teensy-32-usb-microcontroller-development-board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdpoor/WART/HEAD/images/teensy-32-usb-microcontroller-development-board.jpg -------------------------------------------------------------------------------- /wart.ino: -------------------------------------------------------------------------------- 1 | #include "wart.h" 2 | 3 | void setup() { 4 | Serial1.begin(115200); // serial port for PWM 5 | while (!Serial1) { 6 | ; // wait for serial port to connect. 7 | } 8 | } 9 | 10 | void loop() { 11 | Serial1.write(wart, sizeof(wart)); 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, 2021 R. Dunbar Poor 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /wart.py: -------------------------------------------------------------------------------- 1 | ## 2 | # @file: wart.py 3 | # 4 | # MIT License 5 | # 6 | # Copyright (c) 2020, 2021 R. Dunbar Poor 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | # 26 | # 27 | # @brief Convert a .WAV file to a "WART" PWM format file 28 | # 29 | # This python script reads a .wav file and emits a array of bytes in C format 30 | # that encode the audio in pulse-width modulation (PWM) format. To play the 31 | # resulting data, output the array to a UART through a low-pass filter. 32 | # 33 | # Synopsis: 34 | # 35 | # python [arguments] 36 | # 37 | # where arguments can be: 38 | # 39 | # -i default wart.wav 40 | # -o default wart.h 41 | # 42 | # Note 1: The input file MUST be 8 bit unsigned, mono channel. 43 | # Note 2: The output file is designed to be output with a buad rate that is 10x 44 | # the sample rate of the input file. For example, if the input file has 45 | # a sample rate of 11520 Hz, the UART should be configured for 115200 46 | # baud. 47 | # 48 | # @author R. D. Poor 49 | # 50 | # @date August 2020 51 | 52 | import wave 53 | import os 54 | 55 | class Wart(object): 56 | 57 | SYMBOLS = [ 58 | 0b00000000, # 0.1 duty cycle 59 | 0b10000000, # 0.2 duty cycle 60 | 0b11000000, # 0.3 duty cycle 61 | 0b11100000, # 0.4 duty cycle 62 | 0b11110000, # 0.5 duty cycle 63 | 0b11111000, # 0.6 duty cycle 64 | 0b11111100, # 0.7 duty cycle 65 | 0b11111110, # 0.8 duty cycle 66 | 0b11111111, # 0.9 duty cycle 67 | ] 68 | 69 | # Convert a uint8_t to one of the 9 PWM symbols 70 | SAMPLE_MAP = [0] * 256 71 | 72 | def __init__(self, options): 73 | self.options = options 74 | self.init_sample_map() 75 | 76 | def convert_file(self): 77 | with wave.open(self.options.infile, mode='rb') as wavi: 78 | with open(self.wart_h_filename(), "w") as f: 79 | self.generate_c_file(wavi, f) 80 | 81 | def wart_h_filename(self): 82 | if self.options.outfile != None: 83 | return self.options.outfile 84 | root, ext = os.path.splitext(self.options.infile) 85 | return root + ".h" 86 | 87 | def generate_c_file(self, wavi, f): 88 | self.write_c_preamble(wavi, f) 89 | while True: 90 | bytes = wavi.readframes(32) 91 | if len(bytes) == 0: break 92 | self.process_c_line(bytes, f) 93 | self.write_c_postamble(wavi, f) 94 | 95 | def write_c_preamble(self, wavi, f): 96 | print("#ifndef _WART_H_\r\n" + 97 | "#define _WART_H_\r\n\r\n" + 98 | "#include \r\n\r\n" + 99 | "const uint8_t wart[] = {", file=f) 100 | 101 | def process_c_line(self, bytes, f): 102 | mapped = ['0x{:02x},'.format(self.map_sample(b)) for b in bytes] 103 | print(' ' + ''.join(mapped), file=f) 104 | 105 | def write_c_postamble(self, wavi, f): 106 | print("};\r\n" + 107 | "#endif // #ifndef _WART_H_", file=f) 108 | 109 | def map_sample(self, b): 110 | return self.SAMPLE_MAP[b] 111 | 112 | def init_sample_map(self): 113 | for i in range(256): 114 | j = self.lerp(i, 0, 256, 0, len(self.SYMBOLS)) 115 | # print(i, j) 116 | self.SAMPLE_MAP[i] = self.SYMBOLS[int(j)] 117 | 118 | @classmethod 119 | def lerp(cls, x, x0, x1, y0, y1): 120 | return y0 + (x-x0)*(y1-y0)/(x1-x0) 121 | 122 | if __name__ == "__main__": 123 | 124 | import argparse 125 | 126 | parser = argparse.ArgumentParser(description="WART converter") 127 | 128 | parser.add_argument('-i', '--infile', default="wart.wav", 129 | help='input wav file') 130 | parser.add_argument('-o', '--outfile', 131 | help='output h file') 132 | options = parser.parse_args() 133 | 134 | Wart(options).convert_file() 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WART 2 | WART Audio: Play encoded .WAV files with just a UART and a speaker 3 | 4 | Question: Since a UART can only generate ones and zeros (or high and low voltages), how can you use it to play audio? 5 | 6 | Answer: Use the UART as a pulse width modulator. 7 | 8 | ## Short Form: 9 | 10 | WART Audio uses a UART as a simple Pulse Width Modulator (PWM). Sending a stream of 0x00 bytes, you get a 10% duty cycle because the stop bit is always true. Similarly, a stream of 0xff bytes gets you a 90% duty cycle because the start bit is always false. Between those two extremes, you can use the UART to generate nine different levels of PWM. After you put its output through a low-pass filter, this is equivalent to a 3.17 bit DAC -- not high-fidelity by any means, but even the simplest microcontoller can use this approach to play passable audio. 11 | 12 | The script [wart.py](https://github.com/rdpoor/WART/blob/master/wart.py) converts a .wav file into a C-formatted byte array that you incorporate into your microcontroller code. Just a few lines of code are enough to play the array out of the serial port, which you connect to a speaker with an appropriate driver and -- voila -- you get audio. 13 | 14 | ## Using a UART to generate PWM 15 | 16 | This graphic shows how you can use an ordinary UART to generate Pulse Width Modulated signals. The UART is configured for standard `8-n-1` encoding, meaning that each transmitted byte begins with one start bit (always low), followed by eight data bits (least significant bit transmitted first), and ending with one stop bit (always high). 17 | 18 | ![WART Encoding](https://github.com/rdpoor/WART/blob/master/images/WART2.png "WART Encoding") 19 | 20 | 21 | ## wart.h 22 | The output of the python script is a C-compliant `wart.h` file that looks something like this: 23 | 24 | ```c 25 | #ifndef _WART_H_ 26 | #define _WART_H_ 27 | 28 | #include 29 | 30 | const uint8_t wart[] = { 31 | 0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xe0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xe0,0xf0,0xf0,0xf0, 32 | 0xf0,0xe0,0xe0,0xe0,0xe0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf8,0xf8,0xf0,0xf8,0xf8,0xf0,0xf0,0xf0,0xf0,0xf0, 33 | ... 34 | 0xf8,0xf8,0xf8,0xf0,0xf8,0xf8,0xf8,0xf0,0xf0,0xf0,0xf0,0xf0,0xfc,0xf8,0xf0,0xf8,0xfc,0xfc,0xf8,0xf8,0xf8,0xf0,0xf0,0xf0,0xe0,0xc0,0xf0,0xf8,0xe0,0xc0,0xe0,0xe0, 35 | 0xe0,0xf0,0xe0,0xe0,0xe0,0xf0,0xf8,0xf0,0xe0,0xe0,0xe0,0xf0,0xf0,0xf8,0xf0,0xf0,0xf0,0xf8,0xfc,0xf8,0xf8,0xf8,0xf0,0xe0,0xe0,0xe0,0xc0,0xe0,0xf0,0xe0,0xe0,0xf0, 36 | 0xf0,0xf0,0xf8,0xf8,0xf0,0xf0,0xf0,0xf0,0xe0,0xe0,0xc0,0xc0,0xe0,0xf0,0xf8,0xe0,0xc0,0xe0,0xf0,0xf0,0xf0,0xe0,0xc0,0xc0,0xf0,0xf0,0xc0,0xc0,0xf0,0xf0,0xf0,0xf0, 37 | 0xf0,0xf0,0xf8,0xf8,0xf0,0xe0,0xf0,0xf8,0xf0,0xf0,0xe0,0xc0,0xe0,0xf0,0xf8,0xfc,0xf0,0xf0,0xf8,0xfc,0xf8,0xf8,0xf8,0xf0,0xf0,0xf8,0xf8,0xe0,0xe0,0xf0,0xf8,0xf0, 38 | }; 39 | #endif // #ifndef _WART_H_ 40 | ``` 41 | 42 | ## Arduino Sketch 43 | 44 | The following sketch is all that's needed to play a WART-encoded file. Note that in this case, we're using the `Serial1` object whose transmit data appears on pin 3 of the Teensy 3.2 board. (The regular `Serial` object outputs over the USB connection.) 45 | 46 | ```c 47 | // file: wart.ino 48 | #include "wart.h" 49 | 50 | void setup() { 51 | Serial1.begin(115200); // Use Serial1 TXD for PWM output 52 | while (!Serial1) { 53 | ; // wait for serial port to connect. 54 | } 55 | } 56 | 57 | void loop() { 58 | Serial1.write(wart, sizeof(wart)); 59 | } 60 | ``` 61 | 62 | ## The Teensy 3.2 Schematic 63 | Since the Teensy's UART output lacks sufficient oomph (that's a technical term) to power a speaker, a simple transistor driver will do the job. There are many more sophisticated approaches you could take (use an H bridge to double the effective power, use a real low-pass filter), but this simplistic approach is in keeping with WART's "quick and easy" philosophy. 64 | 65 | ![WART Schematic](https://github.com/rdpoor/WART/blob/master/images/WART.png "WART Schematic") 66 | 67 | Here's how it looks on a solderless breadboard: 68 | 69 | ![WART Layout](https://github.com/rdpoor/WART/blob/master/images/IMG_0561.JPG "WART Layout") 70 | 71 | And here's a closeup showing the resistor and transistor drive circuitry: 72 | 73 | ![WART Closeup](https://github.com/rdpoor/WART/blob/master/images/IMG_0573.JPG "WART Closeup") 74 | 75 | ## To create your own WART audio system 76 | 77 | The following steps assume that you're using the Arduino application and a Teensy 3.2, but the general concepts should work with just about any IDE and processor. 78 | 79 | ### Assemble the hardware 80 | 81 | In our example, we used a Teensy 3.2 board, but WART Audio will work with just about any microcontroller. The only requirements are: 82 | - it must have enough program space to hold the sample array 83 | - it must have a UART capable of 115200 baud rate 84 | - its serial driver must be capable of writing bytes to the UART without interruption 85 | 86 | Using the schematic shown above as a guide, build the speaker driver. It requires one 10K resistor, a general purpose NPN transistor and 5V source. 87 | 88 | ### Prepare the firmware 89 | 90 | Assuming that you're using the Arduino application, create a new sketch with [the code listed above](https://github.com/rdpoor/WART/blob/master/wart.ino). 91 | 92 | ### Prepare your audio file 93 | 94 | Use Audacity or sox or your favorite audio tool to create a file with the following properties: 95 | - Number of Channels:1 (Mono) 96 | - Sampling Rate: 11520 97 | - Encoding: .WAV 8 bit unsigned 98 | 99 | For best results, make sure the sound file has been normalized for maximum dynamic range. 100 | The following steps assume the resulting file is named `my_sound.wav`, but of 101 | course you can name it whatever you like. 102 | 103 | ### Convert the file 104 | 105 | - [Download the wart.py python script](https://github.com/rdpoor/WART/blob/master/wart.py) from the github repository. 106 | - In a shell script, invoke `python wart.py -i /my_sound.wav` -o wart.h 107 | 108 | This will create wart.h -- similar to what's shown above -- in the current directory. 109 | 110 | ### Build and run the Arduino sketch 111 | 112 | Move the `wart.h` file into the same directory as the `wart.ino` sketch. Then 113 | launch the Arduino application, open the `wart.ino` sketch, then compile and run it. 114 | 115 | ### Impatient? 116 | 117 | Skip the steps about preparing and converting an audio file, and use the `wart.h` file 118 | already in the project to test your hardware. Management assumes no responsibility for 119 | compaints from your neighbors! 120 | --------------------------------------------------------------------------------