├── .gitignore ├── README.md └── ar_bytebeat.pde /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [bytebeat synthesizer](http://canonical.org/~kragen/bytebeat) 2 | implemented on the Arduino. It's not the first bytebeat synthesizer on 3 | the Arduino, but I think it's the first that does real-time composite 4 | video visualizations of the signal, using the [TVout 5 | library](https://github.com/Diex/ar_tvout) hacked to remove its audio 6 | output. 7 | 8 | It doesn't have any user interface yet; you have to edit the code to 9 | change which formula it's producing. 10 | 11 | I'm running it on an Arduino Duemilanove. Pin 11 has the audio signal, 12 | pin 9 has sync (through a 1-kilohm resistor), and pin 7 has video 13 | (through a 470-ohm resistor). 14 | 15 | ![(photo)](http://farm8.staticflickr.com/7196/6935862575_d106936d0f_z_d.jpg) 16 | 17 | [Photo by Beatrice Murch, cc-by] 18 | (http://www.flickr.com/photos/blmurch/6935862575/sizes/z/in/photostream/) 19 | -------------------------------------------------------------------------------- /ar_bytebeat.pde: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "wiring_private.h" 4 | #undef round 5 | 6 | TVout TV; 7 | 8 | long t = 0; 9 | char i = 0; 10 | 11 | const int buffer_log_size = 7; 12 | static unsigned char buffer[1 << buffer_log_size]; 13 | typedef unsigned char bufidx; 14 | static bufidx front = 0; 15 | static volatile bufidx rear = 0; 16 | 17 | //static inline bufidx next(bufidx cur) 18 | static inline unsigned char next(unsigned char cur) 19 | { 20 | return (cur + 1) & ((1 << buffer_log_size) - 1); 21 | } 22 | 23 | static inline char put(char v) 24 | { 25 | if (rear == next(front)) return 0; 26 | buffer[unsigned(front)] = v; 27 | front = next(front); 28 | return 1; 29 | } 30 | 31 | static inline char get(char *where) 32 | { 33 | register bufidx r = rear; 34 | if (r == front) return 0; 35 | *where = buffer[unsigned(r)]; 36 | rear = next(r); 37 | return 1; 38 | } 39 | 40 | // a place to stick a number to debug with 41 | static unsigned char samples_spat_out_by_asm; 42 | 43 | void our_hbi_hook() { 44 | if (++i != 2) return; 45 | i = 0; 46 | char c; 47 | if (get(&c)) { 48 | OCR2A = c; // set PWM duty cycle 49 | } else { 50 | samples_spat_out_by_asm++; 51 | } 52 | } 53 | 54 | // This stays interesting for ten minutes or so at least, 55 | // and repeats about every twenty: 56 | static inline char crowd() 57 | { 58 | // unoptimized formula: 59 | // ((t<<1)^((t<<1)+(t>>7)&t>>12))|t>>(4-(1^7&(t>>19)))|t>>7 60 | unsigned ut = unsigned(t); 61 | char t1 = char(ut) << 1; 62 | unsigned t7 = ut >> 7; 63 | long t12 = t >> 12; 64 | return (t1 ^ (t1 + t7 & t12)) | ut >> (4 - (1 ^ 7 & char(t12 >> 7))) | t7; 65 | } 66 | 67 | static inline char triangle_bells() 68 | { 69 | char f = (unsigned char)((unsigned char)((unsigned char)(t >> 11) % (unsigned char)63 ^ (0x15 + t >> 12)) %10*2)%13*4; 70 | // unoptimized formula: 71 | // return (f = ((t >> 11) % 63 ^ (0x15 + t >> 12)) %10*2%13*4, ((((t * f & 256) == 0) - 1 ^ t * f) & 255) >> ((t >> 7 + (t >> 13 & 1)) & 7)); 72 | unsigned short ust = t; 73 | // Some notes on frequency. 74 | // ust >> 7 & 7 uses the last 10 bits of ust. (Well, the top three of them, anyway.) 75 | // So it should repeat every 1024 samples. 76 | // ust >> 8 & 7 should repeat every 2048 samples. 77 | // ust >> 13 & 1 should, by the same logic, repeat every 16384 samples. 78 | // So the beat structure should be eight beats of 1024 samples followed by four beats of 2048 samples. 79 | // Audacity tells me that one of these 16384-sample beats runs from 1.000s in my recording to 3.341s. 80 | // So it's taking 2.341 seconds instead of the 2.048 seconds it should take, which is really fairly large. 81 | // I mean, that's 14%, about 2.3 semitones. I'm only getting 6999 samples per second. 82 | // I'm not sure what to do about that. Maybe skip calculating 14% of the samples? Play 28% of the samples 83 | // for only a single scan line instead of two scan lines? 84 | // Audacity also shows a disturbing amount of high-frequency energy, including a peak at 12726Hz (0.55 samples) 85 | // at -37dB, louder than any of the easily audible sounds! 86 | // I see what looks like a regular oscillation with a period of about 3.3 of my 44.1kHz samples in Audacity, 87 | // which would be about 13kHz; I had chalked it up to PWM but I think it's too low-frequency. 88 | // And I may be imagining things but I think I can hear the 13kHz whine on the TV as well. 89 | return (((((ust * f & 256) == 0) - 1 ^ ust * f) & 255) >> ((ust >> 7 + (ust >> 13 & 1)) & 7)); 90 | } 91 | 92 | void generate_samples() 93 | { 94 | for (;;) { 95 | char ft; 96 | char sample = //t*(((t>>12)|(t>>8))&(63&(t>>4))); 97 | // This works okay and has an interesting rhythm, but I 98 | // wonder if it may be better off without the second line 99 | // and corresponding mixing: 100 | // (((((t>>6|t<<1)+(t>>5|t<<3|t>>3)|t>>2|t<<1) & t>>12) ^ t>>16) & 255) 101 | // >> 1 | ((t<<1&t>>9|t+1023>>9) & 255) >> 1 102 | // ; 103 | // This innocent-looking formula used to be really slow until I added char(). 104 | // Should repeat after about 4 hours. Too bad it sounds terrible. 105 | // char(char(t)<<(7&(t>>12)))+(t<<1)&t>>9|t+(t>>10)>>9; 106 | // too slow, except when t was a 16-bit int: 107 | // t^t%255; 108 | // This is a microcontroller-friendly way to do t^t%255: 109 | // t ^ (char(t) + char(int(t) >> 8)); 110 | // and a little more interesting: 111 | // t ^ (char(t) + char(int(t) >> 8)) | short(t) >> 6 | t >> 9; 112 | // and more interesting still: 113 | // crowd(); 114 | // skurk-raer: one of these two branches is too slow with longs: 115 | // ((t&4096)?((t*(t^t%255)|(t>>4))>>1):(t>>3)|((t&8192)?t<<2:t)); 116 | // optimized for microcontroller: 117 | // ((t&4096)?((t*(t^(char(t)+char(int(t)>>8)))|(t>>4))>>1):(t>>3)|((t&8192)?t<<2:t)); 118 | // this one ("laser boots") is still too slow: 119 | // 255-((1L<<28)/(1+(t^0x5800)%0x8000) ^ t | t >> 4 | -t >> 10); 120 | // This bowdlerized version supposedly worked with the Arduino compiler on the NAML 121 | // machine, but on inexorable it's too slow: 122 | // 255-((1<<28)/(1+(t^0x5800)%0x8000) ^ t | t >> 4 | -t >> 10); 123 | // Explore the space of all possible 8-beat rhythms in less than an hour. 124 | // It had some minor vsync problems. 125 | // (t<<1 ^ (t + (t >> 8))) | t >> 2 | (char(t>>15^t>>16^0x75)>>(7&t>>10)&1?0:-1); 126 | // The same rhythm generator, with Ryg's Chaos Theory melody instead: 127 | // t*2*(char((t>>10)^(t>>10)-2)%11) | t >> 2 | (char(t>>15^t>>16^0x75)>>(7&t>>10)&1?0:-1); 128 | // a sort of salsa beat; this one has some kind of incompatibility with JS: 129 | // (t*t>>(4-((t>>14)&7)))*t|(t&t-(2047&~(t>>7)))>>5|t>>3; 130 | // A triangle wave! 131 | // 8*t & 0x100 ? -8*t-1 : 8*t; 132 | // A simple square wave! 133 | // (t & 32) << 2; 134 | // t & (t >> 8); 135 | // t*(((t>>12)|(t>>8))&(63&(t>>4))); 136 | // (t*5&t>>7)|(t*3&t>>10); 137 | // Nice and rhythmic and interesting: 138 | // (t>>6|t<<1)+(t>>5|t<<3|t>>3)|t>>2|t<<1; 139 | // triangle_bells(); 140 | // Simple reflected binary Gray code: 141 | // t ^ t >> 1; 142 | // pseudo-sinewave: 143 | // ((t & 15) * (-t & 15) ^ !(t & 16) - 1) + 128; 144 | // pseudo-sinewave bells: 145 | (ft = t * (1 + (t >> 13 & 3 ^ t >> 18 & 3 ^ 1) << (t >> 15 & 1)), (((ft & 15) * (-ft & 15) ^ !(ft & 16) - 1) >> (t >> (7 + (t >> 13 & 3 ^ 1)) & 7)) + 128); 146 | if (!put(sample)) break; 147 | t++; 148 | } 149 | //if (BUFFER_SIZE < 128) t += 128 - BUFFER_SIZE; 150 | } 151 | 152 | const int height = 86; 153 | const int width = 120; 154 | 155 | void setup() 156 | { 157 | // audio setup 158 | pinMode(11, OUTPUT); 159 | TV.set_hbi_hook(&our_hbi_hook); 160 | 161 | // connect pwm to pin on timer 2 162 | sbi(TCCR2A, COM2A1); 163 | TCCR2B = TCCR2B & 0xf8 | 0x01; // no prescaling on clock select 164 | 165 | ///////////////////////// 166 | TV.begin(NTSC, width, height); 167 | TV.select_font(font6x8); 168 | } 169 | 170 | void loop() 171 | { 172 | //TV.clear_screen(); 173 | 174 | //TV.print(int((unsigned char)buffer[0]), 16); 175 | //TV.print(int(samples_spat_out_by_asm)); 176 | //TV.print(' '); 177 | 178 | for (unsigned char i = 10; i < height; i++) { 179 | generate_samples(); 180 | unsigned char sample = buffer[i]; 181 | TV.fill_line(i, 0, width, 0); 182 | TV.screen[i * width/8] = sample; 183 | TV.fill_line(i, 184 | width/2 - sample*5/32, 185 | width/2 + sample*5/32, 186 | 1); 187 | } 188 | } 189 | 190 | --------------------------------------------------------------------------------