├── README.md ├── Tsunami.cpp ├── Tsunami.h ├── examples └── TsunamiDemo │ └── TsunamiDemo.ino └── keywords.txt /README.md: -------------------------------------------------------------------------------- 1 | Tsunami-Arduino-Serial-Library 2 | ================================== 3 | 4 | Tsunami Serial Control Arduino Library 5 | 6 | Tsunami is a 3.3V device and its inputs, specifically the triggers and serial RX 7 | pins are not 5V tolerant. For this reason, you must either use a 3.3V Arduino or 8 | a level shifter. 9 | 10 | The library assumes that you are using an Arduino with at least one extra hardware 11 | serial port, such as an Teensy or M0, and by default uses Serial1. If you need to 12 | change this, or wish to use AltSoftSerial, you'll need to make one small change to 13 | the library's **Tsunami.h** file. Near the top of the file, look for: 14 | 15 | ``` 16 | // ================================================================== 17 | // The following defines are used to control which serial class is 18 | // used. Uncomment only the one you wish to use. If all of them are 19 | // commented out, the library will use Hardware Serial 20 | #define __WT_USE_SERIAL1__ 21 | //#define __WT_USE_SERIAL2__ 22 | //#define __WT_USE_SERIAL3__ 23 | //#define __WT_USE_ALTSOFTSERIAL__ 24 | // ================================================================== 25 | ``` 26 | 27 | Comment out the `__WT_USE_SERIAL1__` line and uncomment the line corresponding 28 | to the port you want to use. If all the lines are commented out, the library 29 | library will use Serial. 30 | 31 | I make no attempt to throttle the amount of messages that are sent. If you send 32 | continuous volume or sample-rate commands at full speed, you risk overflowing 33 | Tsunami's serial input buffer and/or causing clicks in Tsunami's audio output 34 | due to excessive serial interrupt processing stealing cycles from audio playback. 35 | If you are connecting a continuous controller that can change rapidly for volume 36 | or sample-rate control, you should use a timer to send changes only every 10 or 37 | more msecs. You can, of course, experiment with this. If you're only ever playing 38 | 1 or 2 tracks at a time, you'll likely be able to get away with sending volume 39 | changes more frequently than if you are playing 12 tracks at a time. 40 | 41 | The library includes example sketches that demonstrate many of the library 42 | commands with a set of demo tracks which you can download from 43 | 44 | [https://www.robertsonics.com/blog/2015/04/25/arduino-serial-control-tutorial] 45 | 46 | Usage: 47 | ====== 48 | 49 | In all cases below, the range for **t** (track number) is 1 through 4096, the range 50 | for **out** (output number) is 1 through 4 for the stereo firmware, and 1 through 8 51 | for the mono firmware. 52 | 53 | Tsunami tsunami; 54 | 55 | **tsunami.start()** - you must call this method first to initialize the serial 56 | communications. 57 | 58 | **tsunami.getVersion(char *pDst, int len)** - this function will return **len** bytes of 59 | the Tsunami version string to the location specified by **pDst**. The function 60 | returns TRUE if successful, and FALSE if the string is not available. This 61 | function requires bi-directional communication with Tsunami. 62 | 63 | **tsunami.getNumTracks()** - Returns number of tracks on Tsunami's microSD card 64 | This function requires bi-directional communication with Tsunami. 65 | 66 | **tsunami.setReporting(bool enable)** - this function enables (TRUE) or disables 67 | (FALSE) track reporting. When enabled, the Tsunami will send a message whenever 68 | a track starts or ends, specifying the track number. Provided you call update() 69 | periodically, the library will use these messages to maintain status of all tracks, 70 | allowing you to query if particular tracks are playing or not. 71 | 72 | **tsunami.setTriggerBank(int bank)** - this function sets the trigger bank. The bank 73 | range is 1 - 32. Each bank will offset the normal trigger function track assignment 74 | by 16. For bank 1, the default, trigger one maps to track 1. For bank 2, trigger 1 75 | maps to track 17, trigger 2 to track 18, and so on. 76 | 77 | **tsunami.update()** - this function should be called periodically when reporting is 78 | enabled. Doing so will process any incoming serial messages and keep the track status 79 | up to date. 80 | 81 | **tsunami.isTrackPlaying(int trk)** - If reporting has been enabled, this function can be 82 | used to determine if a particular track is currently playing. 83 | 84 | **tsunami.flush()** - This function clears Tsunami's communication buffer and resets 85 | the local track status info. 86 | 87 | **tsunami.masterGain(int out, int gain)** - this function immediately sets the gain of the 88 | specific stereo output to the specified value. The range for gain is -70 to +4. If 89 | audio is playing, you will hear the result immediately. If audio is not playing, 90 | the new gain will be used the next time a track is started. 91 | 92 | **tsunami.inputGain(int gain)** - this function immediately sets the gain of the 93 | stereo input to the specified value. The range for gain is -70 to +4. If audio 94 | audio is playing, you will hear the result immediately. 95 | 96 | **tsunami.samplerateOffset(int out, int offset)** - this function immediately sets sample-rate offset, 97 | or playback speed / pitch, of the specified stereo output. The range for 98 | for the offset is -32767 to +32676, giving a speed range of 1/2x to 2x, or a 99 | pitch range of down one octave to up one octave. If audio is playing, you will 100 | hear the result immediately. If audio is not playing, the new sample-rate offset 101 | will be used the next time a track is started. 102 | 103 | **tsunami.trackPlaySolo(int t, int out, bool lock)** - this function stops any and all tracks 104 | that are currently playing and starts track number **t** from the beginning. The track is 105 | routed to the specified stereo output. If **lock** is TRUE, the track will not be subject to 106 | Tsunami's voice stealing algorithm. 107 | 108 | **tsunami.trackPlayPoly(int t, int out, bool lock)** - this function starts track number 109 | **t** from the beginning, blending it with any other tracks that are currently playing, 110 | including potentially another copy of the same track. The track is routed to the specified 111 | stereo output. If **lock** is TRUE, the track will not be subject to Tsunami's voice stealing 112 | algorithm. 113 | 114 | **tsunami.trackLoad(int t, int out, bool lock)** - this function loads track number **t** 115 | and pauses it at the beginning of the track. Loading muiltiple tracks and then un-pausing 116 | the all with resumeAllInSync() function below allows for starting multiple tracks in 117 | sample sync. The track is routed to the specified stereo output. If **lock** is TRUE, the 118 | track will not be subject to Tsunami's voice stealing algorithm. 119 | 120 | **tsunami.trackStop(int t)** - this function stops track number **t** if it's currently 121 | playing. If track t is not playing, this function does nothing. No other 122 | tracks are affected. 123 | 124 | **tsunami.trackPause(int t)** - this function pauses track number **t** if it's currently 125 | playing. If track t is not playing, this function does nothing. Keep in mind 126 | that a paused track is still using one of the 8 voice slots. A voice allocated 127 | to playing a track becomes free only when that sound is stopped or the track 128 | reaches the end of the file (and is not looping). 129 | 130 | **tsunami.trackResume(int t)** - this function resumes track number **t** if it's currently 131 | paused. If track number **t** is not paused, this function does nothing. 132 | 133 | **tsunami.trackLoop(int t, bool enable)** - this function enables (true) or disables 134 | (false) the loop flag for track **t**. This command does not actually start a track, 135 | only determines how it behaves once it is playing and reaches the end. If the 136 | loop flag is set, that track will loop continuously until it's stopped, in which 137 | case it will stop immediately but the loop flag will remain set, or until the loop 138 | flag is cleared, in which case it will stop when it reaches the end of the track. 139 | This command may be used either before a track is started or while it's playing. 140 | 141 | **tsunami.trackGain(int t, int gain)** - this function immediately sets the gain of 142 | track **t** to the specified value. The range for gain is -70 to +10. A value of 143 | 0 (no gain) plays the track at the nominal value in the wav file. This is the 144 | default gain for every track until changed. A value of -70 is completely 145 | muted. If the track is playing, you will hear the result immediately. If the 146 | track is not playing, the gain will be used the next time the track is started. 147 | Every track can have its own gain. 148 | 149 | Because the effect is immediate, large changes can produce ubrupt results. If 150 | you want to fade in or fade out a track, send small changes spaced out at regular 151 | intervals. Increment or decrementing by 1 every 20 to 50 msecs produces nice 152 | smooth fades. Better yet, use the trackFade() function below. 153 | 154 | **tsunami.stopAllTracks()** - this commands stops any and all tracks that are currently 155 | playing. 156 | 157 | **tsunami.resumeAllInSync()** - this command resumes all paused tracks within the same 158 | audio buffer. Any tracks that were loaded using the trackLoad() function will start 159 | and remain sample locked (in sample sync) with one another. 160 | 161 | **tsunami.trackFade(int t, int gain, int time, bool stopFlag)** - this command initiates 162 | a hardware volume fade on track number **t** if it is currently playing. The track 163 | volume will transition smoothly from the current value to the target gain in the 164 | specified number of milliseconds. If the stopFlag is non-zero, the track will be 165 | stopped at the completion of the fade (for fade-outs.) 166 | 167 | **tsunami.setInputMix(int mix)** - this function controls the routing of the audio input 168 | channels. For bits 1 through 4, a "1" causes the 2 input channels to be mixed into 169 | the corresponding output pair. As an example, to route the audio input to output pairs 170 | 1, 2 and 4, the syntax is: setInputMix(IMIX_OUT1 | IMIX_OUT2 | IMIX_OUT4); The routing 171 | is immediate and does no ramping, so to avoid pops, be sure that the input is quiet 172 | when switching. 173 | 174 | **tsunami.setMidiBank(int bank)** - this function sets the MIDI bank. The bank range 175 | is 1 - 32. Each bank will offset the MIDI Note number to track assignment by 128. 176 | For bank 1, the default, MIDI Note number maps to track 1. For bank 2, MIDI Note 177 | number 1 maps to track 129, MIDI Note number 2 to track 130, and so on. 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /Tsunami.cpp: -------------------------------------------------------------------------------- 1 | // ************************************************************** 2 | // Filename: Tsunami.cpp 3 | // Date Created: 11/22/2016 4 | // 5 | // Comments: Robertsonics Tsunami serial control library 6 | // 7 | // Programmers: Jamie Robertson, info@robertsonics.com 8 | // 9 | // ************************************************************** 10 | 11 | #include "Tsunami.h" 12 | 13 | 14 | // ************************************************************** 15 | void Tsunami::start(void) { 16 | 17 | uint8_t txbuf[5]; 18 | 19 | versionRcvd = false; 20 | sysinfoRcvd = false; 21 | TsunamiSerial.begin(57600); 22 | flush(); 23 | 24 | // Request version string 25 | txbuf[0] = SOM1; 26 | txbuf[1] = SOM2; 27 | txbuf[2] = 0x05; 28 | txbuf[3] = CMD_GET_VERSION; 29 | txbuf[4] = EOM; 30 | TsunamiSerial.write(txbuf, 5); 31 | 32 | // Request system info 33 | txbuf[0] = SOM1; 34 | txbuf[1] = SOM2; 35 | txbuf[2] = 0x05; 36 | txbuf[3] = CMD_GET_SYS_INFO; 37 | txbuf[4] = EOM; 38 | TsunamiSerial.write(txbuf, 5); 39 | } 40 | 41 | // ************************************************************** 42 | void Tsunami::flush(void) { 43 | 44 | int i; 45 | 46 | rxCount = 0; 47 | rxLen = 0; 48 | rxMsgReady = false; 49 | for (i = 0; i < MAX_NUM_VOICES; i++) { 50 | voiceTable[i] = 0xffff; 51 | } 52 | while(TsunamiSerial.available()) 53 | i = TsunamiSerial.read(); 54 | } 55 | 56 | 57 | // ************************************************************** 58 | void Tsunami::update(void) { 59 | 60 | int i; 61 | uint8_t dat; 62 | uint8_t voice; 63 | uint16_t track; 64 | 65 | rxMsgReady = false; 66 | while (TsunamiSerial.available() > 0) { 67 | dat = TsunamiSerial.read(); 68 | if ((rxCount == 0) && (dat == SOM1)) { 69 | rxCount++; 70 | } 71 | else if (rxCount == 1) { 72 | if (dat == SOM2) 73 | rxCount++; 74 | else { 75 | rxCount = 0; 76 | //Serial.print("Bad msg 1\n"); 77 | } 78 | } 79 | else if (rxCount == 2) { 80 | if (dat <= MAX_MESSAGE_LEN) { 81 | rxCount++; 82 | rxLen = dat - 1; 83 | } 84 | else { 85 | rxCount = 0; 86 | //Serial.print("Bad msg 2\n"); 87 | } 88 | } 89 | else if ((rxCount > 2) && (rxCount < rxLen)) { 90 | rxMessage[rxCount - 3] = dat; 91 | rxCount++; 92 | } 93 | else if (rxCount == rxLen) { 94 | if (dat == EOM) 95 | rxMsgReady = true; 96 | else { 97 | rxCount = 0; 98 | //Serial.print("Bad msg 3\n"); 99 | } 100 | } 101 | else { 102 | rxCount = 0; 103 | //Serial.print("Bad msg 4\n"); 104 | } 105 | 106 | if (rxMsgReady) { 107 | switch (rxMessage[0]) { 108 | 109 | case RSP_TRACK_REPORT: 110 | track = rxMessage[2]; 111 | track = (track << 8) + rxMessage[1] + 1; 112 | voice = rxMessage[3]; 113 | if (voice < MAX_NUM_VOICES) { 114 | if (rxMessage[4] == 0) { 115 | if (track == voiceTable[voice]) 116 | voiceTable[voice] = 0xffff; 117 | } 118 | else 119 | voiceTable[voice] = track; 120 | } 121 | // ========================== 122 | //Serial.print("Track "); 123 | //Serial.print(track); 124 | //if (rxMessage[4] == 0) 125 | // Serial.print(" off\n"); 126 | //else 127 | // Serial.print(" on\n"); 128 | // ========================== 129 | break; 130 | 131 | case RSP_VERSION_STRING: 132 | for (i = 0; i < (VERSION_STRING_LEN - 1); i++) 133 | version[i] = rxMessage[i + 1]; 134 | version[VERSION_STRING_LEN - 1] = 0; 135 | versionRcvd = true; 136 | // ========================== 137 | //Serial.write(version); 138 | //Serial.write("\n"); 139 | // ========================== 140 | break; 141 | 142 | case RSP_SYSTEM_INFO: 143 | numVoices = rxMessage[1]; 144 | numTracks = rxMessage[3]; 145 | numTracks = (numTracks << 8) + rxMessage[2]; 146 | sysinfoRcvd = true; 147 | // ========================== 148 | ///\Serial.print("Sys info received\n"); 149 | // ========================== 150 | break; 151 | 152 | } 153 | rxCount = 0; 154 | rxLen = 0; 155 | rxMsgReady = false; 156 | 157 | } // if (rxMsgReady) 158 | 159 | } // while (TsunamiSerial.available() > 0) 160 | } 161 | 162 | // ************************************************************** 163 | bool Tsunami::isTrackPlaying(int trk) { 164 | 165 | int i; 166 | bool fResult = false; 167 | 168 | update(); 169 | for (i = 0; i < MAX_NUM_VOICES; i++) { 170 | if (voiceTable[i] == trk) 171 | fResult = true; 172 | } 173 | return fResult; 174 | } 175 | 176 | // ************************************************************** 177 | void Tsunami::masterGain(int out, int gain) { 178 | 179 | uint8_t txbuf[8]; 180 | unsigned short vol; 181 | uint8_t o; 182 | 183 | o = out & 0x07; 184 | txbuf[0] = SOM1; 185 | txbuf[1] = SOM2; 186 | txbuf[2] = 0x08; 187 | txbuf[3] = CMD_MASTER_VOLUME; 188 | txbuf[4] = o; 189 | vol = (unsigned short)gain; 190 | txbuf[5] = (uint8_t)vol; 191 | txbuf[6] = (uint8_t)(vol >> 8); 192 | txbuf[7] = EOM; 193 | TsunamiSerial.write(txbuf, 8); 194 | } 195 | 196 | // ************************************************************** 197 | void Tsunami::inputGain(int gain) { 198 | 199 | uint8_t txbuf[8]; 200 | unsigned short vol; 201 | 202 | txbuf[0] = SOM1; 203 | txbuf[1] = SOM2; 204 | txbuf[2] = 0x07; 205 | txbuf[3] = CMD_INPUT_GAIN; 206 | vol = (unsigned short)gain; 207 | txbuf[4] = (uint8_t)vol; 208 | txbuf[5] = (uint8_t)(vol >> 8); 209 | txbuf[6] = EOM; 210 | TsunamiSerial.write(txbuf, 7); 211 | } 212 | 213 | // ************************************************************** 214 | void Tsunami::setReporting(bool enable) { 215 | 216 | uint8_t txbuf[6]; 217 | 218 | txbuf[0] = SOM1; 219 | txbuf[1] = SOM2; 220 | txbuf[2] = 0x06; 221 | txbuf[3] = CMD_SET_REPORTING; 222 | txbuf[4] = enable; 223 | txbuf[5] = EOM; 224 | TsunamiSerial.write(txbuf, 6); 225 | } 226 | 227 | // ************************************************************** 228 | bool Tsunami::getVersion(char *pDst, int len) { 229 | 230 | int i; 231 | 232 | update(); 233 | if (!versionRcvd) { 234 | return false; 235 | } 236 | for (i = 0; i < (VERSION_STRING_LEN - 1); i++) { 237 | if (i >= (len - 1)) 238 | break; 239 | pDst[i] = version[i]; 240 | } 241 | pDst[++i] = 0; 242 | return true; 243 | } 244 | 245 | // ************************************************************** 246 | int Tsunami::getNumTracks(void) { 247 | 248 | update(); 249 | return numTracks; 250 | } 251 | 252 | 253 | // ************************************************************** 254 | void Tsunami::trackPlaySolo(int trk, int out, bool lock) { 255 | 256 | int flags = 0; 257 | 258 | if (lock) 259 | flags |= 0x01; 260 | trackControl(trk, TRK_PLAY_SOLO, out, flags); 261 | } 262 | 263 | // ************************************************************** 264 | void Tsunami::trackPlayPoly(int trk, int out, bool lock) { 265 | 266 | int flags = 0; 267 | 268 | if (lock) 269 | flags |= 0x01; 270 | trackControl(trk, TRK_PLAY_POLY, out, flags); 271 | } 272 | 273 | // ************************************************************** 274 | void Tsunami::trackLoad(int trk, int out, bool lock) { 275 | 276 | int flags = 0; 277 | 278 | if (lock) 279 | flags |= 0x01; 280 | trackControl(trk, TRK_LOAD, out, flags); 281 | } 282 | 283 | // ************************************************************** 284 | void Tsunami::trackStop(int trk) { 285 | 286 | trackControl(trk, TRK_STOP, 0, 0); 287 | } 288 | 289 | // ************************************************************** 290 | void Tsunami::trackPause(int trk) { 291 | 292 | trackControl(trk, TRK_PAUSE, 0, 0); 293 | } 294 | 295 | // ************************************************************** 296 | void Tsunami::trackResume(int trk) { 297 | 298 | trackControl(trk, TRK_RESUME, 0, 0); 299 | } 300 | 301 | // ************************************************************** 302 | void Tsunami::trackLoop(int trk, bool enable) { 303 | 304 | if (enable) 305 | trackControl(trk, TRK_LOOP_ON, 0, 0); 306 | else 307 | trackControl(trk, TRK_LOOP_OFF, 0, 0); 308 | } 309 | 310 | // ************************************************************** 311 | void Tsunami::trackControl(int trk, int code, int out, int flags) { 312 | 313 | uint8_t txbuf[10]; 314 | uint8_t o; 315 | 316 | o = out & 0x07; 317 | txbuf[0] = SOM1; 318 | txbuf[1] = SOM2; 319 | txbuf[2] = 0x0a; 320 | txbuf[3] = CMD_TRACK_CONTROL; 321 | txbuf[4] = (uint8_t)code; 322 | txbuf[5] = (uint8_t)trk; 323 | txbuf[6] = (uint8_t)(trk >> 8); 324 | txbuf[7] = (uint8_t)o; 325 | txbuf[8] = (uint8_t)flags; 326 | txbuf[9] = EOM; 327 | TsunamiSerial.write(txbuf, 10); 328 | } 329 | 330 | // ************************************************************** 331 | void Tsunami::stopAllTracks(void) { 332 | 333 | uint8_t txbuf[5]; 334 | 335 | txbuf[0] = SOM1; 336 | txbuf[1] = SOM2; 337 | txbuf[2] = 0x05; 338 | txbuf[3] = CMD_STOP_ALL; 339 | txbuf[4] = EOM; 340 | TsunamiSerial.write(txbuf, 5); 341 | } 342 | 343 | // ************************************************************** 344 | void Tsunami::resumeAllInSync(void) { 345 | 346 | uint8_t txbuf[5]; 347 | 348 | txbuf[0] = SOM1; 349 | txbuf[1] = SOM2; 350 | txbuf[2] = 0x05; 351 | txbuf[3] = CMD_RESUME_ALL_SYNC; 352 | txbuf[4] = EOM; 353 | TsunamiSerial.write(txbuf, 5); 354 | } 355 | 356 | // ************************************************************** 357 | void Tsunami::trackGain(int trk, int gain) { 358 | 359 | uint8_t txbuf[9]; 360 | unsigned short vol; 361 | 362 | txbuf[0] = SOM1; 363 | txbuf[1] = SOM2; 364 | txbuf[2] = 0x09; 365 | txbuf[3] = CMD_TRACK_VOLUME; 366 | txbuf[4] = (uint8_t)trk; 367 | txbuf[5] = (uint8_t)(trk >> 8); 368 | vol = (unsigned short)gain; 369 | txbuf[6] = (uint8_t)vol; 370 | txbuf[7] = (uint8_t)(vol >> 8); 371 | txbuf[8] = EOM; 372 | TsunamiSerial.write(txbuf, 9); 373 | } 374 | 375 | // ************************************************************** 376 | void Tsunami::trackFade(int trk, int gain, int time, bool stopFlag) { 377 | 378 | uint8_t txbuf[12]; 379 | unsigned short vol; 380 | 381 | txbuf[0] = SOM1; 382 | txbuf[1] = SOM2; 383 | txbuf[2] = 0x0c; 384 | txbuf[3] = CMD_TRACK_FADE; 385 | txbuf[4] = (uint8_t)trk; 386 | txbuf[5] = (uint8_t)(trk >> 8); 387 | vol = (unsigned short)gain; 388 | txbuf[6] = (uint8_t)vol; 389 | txbuf[7] = (uint8_t)(vol >> 8); 390 | txbuf[8] = (uint8_t)time; 391 | txbuf[9] = (uint8_t)(time >> 8); 392 | txbuf[10] = stopFlag; 393 | txbuf[11] = EOM; 394 | TsunamiSerial.write(txbuf, 12); 395 | } 396 | 397 | // ************************************************************** 398 | void Tsunami::samplerateOffset(int out, int offset) { 399 | 400 | uint8_t txbuf[8]; 401 | unsigned short off; 402 | uint8_t o; 403 | 404 | o = out & 0x07; 405 | txbuf[0] = SOM1; 406 | txbuf[1] = SOM2; 407 | txbuf[2] = 0x08; 408 | txbuf[3] = CMD_SAMPLERATE_OFFSET; 409 | txbuf[4] = (uint8_t)o; 410 | off = (unsigned short)offset; 411 | txbuf[5] = (uint8_t)off; 412 | txbuf[6] = (uint8_t)(off >> 8); 413 | txbuf[7] = EOM; 414 | TsunamiSerial.write(txbuf, 8); 415 | } 416 | 417 | // ************************************************************** 418 | void Tsunami::setTriggerBank(int bank) { 419 | 420 | uint8_t txbuf[6]; 421 | 422 | txbuf[0] = SOM1; 423 | txbuf[1] = SOM2; 424 | txbuf[2] = 0x06; 425 | txbuf[3] = CMD_SET_TRIGGER_BANK; 426 | txbuf[4] = (uint8_t)bank; 427 | txbuf[5] = EOM; 428 | TsunamiSerial.write(txbuf, 6); 429 | } 430 | 431 | // ************************************************************** 432 | void Tsunami::setInputMix(int mix) { 433 | 434 | uint8_t txbuf[6]; 435 | 436 | txbuf[0] = SOM1; 437 | txbuf[1] = SOM2; 438 | txbuf[2] = 0x06; 439 | txbuf[3] = CMD_SET_INPUT_MIX; 440 | txbuf[4] = (uint8_t)mix; 441 | txbuf[5] = EOM; 442 | TsunamiSerial.write(txbuf, 6); 443 | } 444 | 445 | // ************************************************************** 446 | void Tsunami::setMidiBank(int bank) { 447 | 448 | uint8_t txbuf[6]; 449 | 450 | txbuf[0] = SOM1; 451 | txbuf[1] = SOM2; 452 | txbuf[2] = 0x06; 453 | txbuf[3] = CMD_SET_MIDI_BANK; 454 | txbuf[4] = (uint8_t)bank; 455 | txbuf[5] = EOM; 456 | TsunamiSerial.write(txbuf, 6); 457 | } 458 | 459 | 460 | 461 | -------------------------------------------------------------------------------- /Tsunami.h: -------------------------------------------------------------------------------- 1 | // ************************************************************** 2 | // Filename: Tsunami.h 3 | // Date Created: 11/22/2016 4 | // 5 | // Comments: Robertsonics Tsunami serial control library 6 | // 7 | // Programmers: Jamie Robertson, info@robertsonics.com 8 | // 9 | // ************************************************************** 10 | 11 | #ifndef _20161015_TSUNAMI_H_ 12 | #define _20161015_TSUNAMI_H_ 13 | 14 | #define TSUNAMI_NUM_OUTPUTS 8 15 | 16 | // ================================================================== 17 | // The following defines are used to control which serial class is 18 | // used. Uncomment only the one you wish to use. If all of them are 19 | // commented out, the library will use Hardware Serial 20 | #define __TSUNAMI_USE_SERIAL1__ 21 | //#define __TSUNAMI_USE_SERIAL2__ 22 | //#define __TSUNAMI_USE_SERIAL3__ 23 | //#define __TSUNAMI_USE_ALTSOFTSERIAL__ 24 | // ================================================================== 25 | 26 | #define CMD_GET_VERSION 1 27 | #define CMD_GET_SYS_INFO 2 28 | #define CMD_TRACK_CONTROL 3 29 | #define CMD_STOP_ALL 4 30 | #define CMD_MASTER_VOLUME 5 31 | #define CMD_TRACK_VOLUME 8 32 | #define CMD_TRACK_FADE 10 33 | #define CMD_RESUME_ALL_SYNC 11 34 | #define CMD_SAMPLERATE_OFFSET 12 35 | #define CMD_SET_REPORTING 13 36 | #define CMD_SET_TRIGGER_BANK 14 37 | #define CMD_SET_INPUT_MIX 15 38 | #define CMD_SET_MIDI_BANK 16 39 | #define CMD_INPUT_GAIN 18 40 | 41 | #define TRK_PLAY_SOLO 0 42 | #define TRK_PLAY_POLY 1 43 | #define TRK_PAUSE 2 44 | #define TRK_RESUME 3 45 | #define TRK_STOP 4 46 | #define TRK_LOOP_ON 5 47 | #define TRK_LOOP_OFF 6 48 | #define TRK_LOAD 7 49 | 50 | #define RSP_VERSION_STRING 129 51 | #define RSP_SYSTEM_INFO 130 52 | #define RSP_STATUS 131 53 | #define RSP_TRACK_REPORT 132 54 | 55 | #define MAX_MESSAGE_LEN 32 56 | #define MAX_NUM_VOICES 18 57 | #define VERSION_STRING_LEN 23 58 | 59 | #define SOM1 0xf0 60 | #define SOM2 0xaa 61 | #define EOM 0x55 62 | 63 | #define IMIX_OUT1 0x01 64 | #define IMIX_OUT2 0x02 65 | #define IMIX_OUT3 0x04 66 | #define IMIX_OUT4 0x08 67 | 68 | 69 | #ifdef __TSUNAMI_USE_ALTSOFTSERIAL__ 70 | #include "../AltSoftSerial/AltSoftSerial.h" 71 | #else 72 | #include 73 | #ifdef __TSUNAMI_USE_SERIAL1__ 74 | #define TsunamiSerial Serial1 75 | #define __TSUNAMI_SERIAL_ASSIGNED__ 76 | #endif 77 | #ifdef __TSUNAMI_USE_SERIAL2__ 78 | #define TsunamiSerial Serial2 79 | #define __TSUNAMI_SERIAL_ASSIGNED__ 80 | #endif 81 | #ifdef __TSUNAMI_USE_SERIAL3__ 82 | #define TsunamiSerial Serial3 83 | #define __TSUNAMI_SERIAL_ASSIGNED__ 84 | #endif 85 | #ifndef __TSUNAMI_SERIAL_ASSIGNED__ 86 | #define TsunamiSerial Serial 87 | #endif 88 | #endif 89 | 90 | class Tsunami 91 | { 92 | public: 93 | Tsunami() {;} 94 | ~Tsunami() {;} 95 | void start(void); 96 | void update(void); 97 | void flush(void); 98 | void setReporting(bool enable); 99 | bool getVersion(char *pDst, int len); 100 | int getNumTracks(void); 101 | bool isTrackPlaying(int trk); 102 | void masterGain(int out, int gain); 103 | void inputGain(int gain); 104 | void stopAllTracks(void); 105 | void resumeAllInSync(void); 106 | void trackPlaySolo(int trk, int out, bool lock); 107 | void trackPlayPoly(int trk, int out, bool lock); 108 | void trackLoad(int trk, int out, bool lock); 109 | void trackStop(int trk); 110 | void trackPause(int trk); 111 | void trackResume(int trk); 112 | void trackLoop(int trk, bool enable); 113 | void trackGain(int trk, int gain); 114 | void trackFade(int trk, int gain, int time, bool stopFlag); 115 | void samplerateOffset(int out, int offset); 116 | void setTriggerBank(int bank); 117 | void setInputMix(int mix); 118 | void setMidiBank(int bank); 119 | 120 | private: 121 | void trackControl(int trk, int code, int out, int flags); 122 | 123 | #ifdef __TSUNAMI_USE_ALTSOFTSERIAL__ 124 | AltSoftSerial TsunamiSerial; 125 | #endif 126 | 127 | uint16_t voiceTable[MAX_NUM_VOICES]; 128 | uint8_t rxMessage[MAX_MESSAGE_LEN]; 129 | char version[VERSION_STRING_LEN]; 130 | uint16_t numTracks; 131 | uint8_t numVoices; 132 | uint8_t rxCount; 133 | uint8_t rxLen; 134 | bool rxMsgReady; 135 | bool versionRcvd; 136 | bool sysinfoRcvd; 137 | }; 138 | 139 | #endif 140 | -------------------------------------------------------------------------------- /examples/TsunamiDemo/TsunamiDemo.ino: -------------------------------------------------------------------------------- 1 | // **************************************************************************** 2 | // Sketch: Tsunami Serial Control Example 3 | // Date Created: 11/22/2016 4 | // 5 | // Comments: Demonstrates advanced, two-way, serial control of the Tsunami 6 | // from a 3.3V Arduino. 7 | // 8 | // Programmers: Jamie Robertson, info@robertsonics.com 9 | // 10 | // WARNING: The Tsunami is a 3.3V device. Attempting to connect it to a 5V 11 | // Arduino (such as an UNO) without level-shifters, will likely damage your 12 | // Tsunami! 13 | // 14 | // **************************************************************************** 15 | // 16 | // To use this sketch with an Arduino, you'll need: 17 | // 18 | // 1) An Arduino with an operating voltage of 3.3V, *** NOT 5V ***! 19 | // 2) Download and install the Metro library. 20 | // 3) Connect 3 wires from the Arduino to the Tsunami's serial connector: 21 | // 22 | // Arduino Tsunami 23 | // ======= ======= 24 | // GND <------> GND 25 | // TXn <------> RX 26 | // RXn <------> TX 27 | // 28 | // If you're using an Arduino with at least one extra hardware serial port, 29 | // such as an M0 or Teensy, the Tsunami library is already set to use 30 | // Serial1. If you need to change that, or want to use AltSoftSerial, you 31 | // must make a small edit the Tsunami.h file, according the instructions in 32 | // that file. 33 | // 34 | // 4) Download and install the demo wav files onto the Tsunami's microSD 35 | // card. You can find them here: 36 | // 37 | // http://robertsonics.com/2015/04/25/arduino-serial-control-tutorial/ 38 | // 39 | // You can certainly use your own tracks instead, although the demo may 40 | // not make as much sense. If you do, make sure your tracks are at least 41 | // 10 to 20 seconds long and have no silence at the start of the file. 42 | // 43 | // 5) Open the Arduino Serial Monitor window to see status messages. 44 | 45 | #include // Include the Metro library header 46 | #include // Include the Tsunami library header 47 | //#include // Optional for using AltSoftSerial 48 | 49 | #define LED 13 // our LED 50 | 51 | Tsunami tsunami; // Our Tsunami object 52 | 53 | Metro gLedMetro(500); // LED blink interval timer 54 | Metro gSeqMetro(6000); // Sequencer state machine interval timer 55 | 56 | byte gLedState = 0; // LED State 57 | int gSeqState = 0; // Main program sequencer state 58 | int gRateOffset = 0; // Tsunami sample-rate offset 59 | int gNumTracks; // Number of tracks on SD card 60 | 61 | char gTsunamiVersion[VERSION_STRING_LEN]; // Tsunami version string 62 | 63 | 64 | // **************************************************************************** 65 | void setup() { 66 | 67 | // Serial monitor 68 | Serial.begin(9600); 69 | 70 | // Initialize the LED pin 71 | pinMode(LED,OUTPUT); 72 | digitalWrite(LED,gLedState); 73 | 74 | // We should wait for the Tsunami to finish reset before trying to send 75 | // commands. 76 | delay(1000); 77 | 78 | // Tsunami startup at 57600 79 | tsunami.start(); 80 | delay(10); 81 | 82 | // Send a stop-all command and reset the sample-rate offset, in case we have 83 | // reset while the Tsunami was already playing. 84 | tsunami.stopAllTracks(); 85 | tsunami.samplerateOffset(0, 0); 86 | 87 | // Enable track reporting from the Tsunami 88 | tsunami.setReporting(true); 89 | 90 | // Allow time for the Tsunami to respond with the version string and 91 | // number of tracks. 92 | delay(100); 93 | 94 | } 95 | 96 | 97 | // **************************************************************************** 98 | // This program uses a Metro timer to create a sequencer that steps through 99 | // states at 6 second intervals - you can change this rate above. Each state 100 | // Each state will demonstrate a Tsunami serial control feature. 101 | // 102 | // In this example, some states wait for specific audio tracks to stop playing 103 | // before advancing to the next state. 104 | 105 | void loop() { 106 | 107 | int i; 108 | 109 | // Call update on the Tsunami to keep the track playing status current. 110 | tsunami.update(); 111 | 112 | // Check if the sequencer timer has elapsed and perform the appropriate 113 | // state action if so. States 3 and 5 wait for tracks to stop playing and 114 | // are therefore not in the metro event. They are instead polled after the 115 | // metro check. 116 | if (gSeqMetro.check() == 1) { 117 | 118 | switch (gSeqState) { 119 | 120 | // State 0: Demonstrates how to fade in a music track 121 | case 0: 122 | // First retrieve and print the version and number of tracks 123 | if (tsunami.getVersion(gTsunamiVersion, VERSION_STRING_LEN)) { 124 | Serial.print(gTsunamiVersion); 125 | Serial.print("\n"); 126 | gNumTracks = tsunami.getNumTracks(); 127 | Serial.print("Number of tracks = "); 128 | Serial.print(gNumTracks); 129 | Serial.print("\n"); 130 | } 131 | else 132 | Serial.print("WAV Trigger response not available"); 133 | tsunami.samplerateOffset(0, 0); // Reset sample rate offset to 0 134 | tsunami.masterGain(0, 0); // Reset the master gain to 0dB 135 | 136 | tsunami.trackGain(2, -40); // Preset Track 2 gain to -40dB 137 | tsunami.trackPlayPoly(2, 0, true); // Start Track 2 138 | tsunami.trackFade(2, 0, 2000, false); // Fade Track 2 to 0dB over 2 sec 139 | gSeqState = 1; // Advance to state 1 140 | break; 141 | 142 | // State 1: Demonstrates how to cross-fade music tracks 143 | case 1: 144 | tsunami.trackGain(1, -40); // Preset Track 1 gain to -40dB 145 | tsunami.trackPlayPoly(1, 0, true); // Start Track 1 146 | tsunami.trackFade(1, 0, 3000, false); // Fade Track 1 up to 0db over 3 secs 147 | tsunami.update(); 148 | delay(2000); // Wait 2 secs 149 | tsunami.trackFade(2, -40, 3000, true); // Fade Track 2 down to -40dB over 3 secs and stop 150 | Serial.print("Waiting for Track 2 to finish... "); 151 | gSeqState = 2; // Advance to state 2 152 | break; 153 | 154 | // State 3: Honk the horn 2 times 155 | case 3: 156 | tsunami.trackPlayPoly(5, 0, true); // Start Track 5 poly 157 | tsunami.update(); 158 | delay(500); 159 | tsunami.trackStop(5); // Stop Track 5 160 | tsunami.update(); 161 | delay(250); 162 | tsunami.trackPlayPoly(5, 0, true); // Start Track 5 poly 163 | tsunami.update(); 164 | delay(500); 165 | tsunami.trackStop(5); // Stop Track 5 166 | gSeqState = 4; // Advance to state 4 167 | break; 168 | 169 | // State 4: Fade out and stop dialog 170 | case 4: 171 | tsunami.trackLoop(4, 0); // Disable Track 4 looping 172 | tsunami.trackFade(4, -50, 5000, true); // Fade Track 4 to -50dB and stop 173 | Serial.print("Waiting for Track 4 to finish... "); 174 | gSeqState = 5; // Advance to state 5 175 | break; 176 | 177 | // State 6: Demonstrates preloading tracks and starting them in sample- 178 | // sync, and real-time samplerate control (pitch bending); 179 | case 6: 180 | tsunami.trackLoad(6, 0, true); // Load and pause Track 6 181 | tsunami.trackLoad(7, 0, true); // Load and pause Track 7 182 | tsunami.trackLoad(8, 0, true); // Load and pause Track 8 183 | tsunami.resumeAllInSync(); // Start all in sample sync 184 | 185 | // Decrement the sample rate offset from 0 to -32767 (1 octave down) 186 | // in 10 ms steps 187 | gRateOffset = 0; 188 | for (i = 0; i < 127; i++) { 189 | gRateOffset -= 256; 190 | tsunami.samplerateOffset(0, gRateOffset); 191 | delay(10); 192 | } 193 | gRateOffset = -32767; 194 | tsunami.samplerateOffset(0, gRateOffset); 195 | 196 | // Hold for 1 second 197 | delay(1000); 198 | 199 | // Now increment to +32767 (1 octave up) in 10ms steps 200 | for (i = 0; i < 255; i++) { 201 | gRateOffset += 256; 202 | tsunami.samplerateOffset(0, gRateOffset); 203 | delay(10); 204 | } 205 | gRateOffset = 32767; 206 | tsunami.samplerateOffset(0, gRateOffset); 207 | 208 | // Hold for 1 second, the stop all tracks 209 | delay(1000); 210 | tsunami.stopAllTracks(); // Stop all 211 | gSeqState = 0; // Advance to state 0 212 | break; 213 | 214 | } // switch 215 | 216 | } // if (gSeqState.check() == 1) 217 | 218 | // State 2: Wait for Track 2 to stop, then fade down the music and start the 219 | // dialog track looping. 220 | if (gSeqState == 2) { 221 | gSeqMetro.reset(); // Reset the sequencer metro 222 | if (!tsunami.isTrackPlaying(2)) { 223 | Serial.print("Track 2 done\n"); 224 | tsunami.trackFade(1, -6, 500, false); // Lower the music volume 225 | tsunami.trackLoop(4, 1); // Enable Track 4 looping 226 | tsunami.trackPlayPoly(4, 0, true); // Start Track 4 poly 227 | gSeqState = 3; // Advance to state 3; 228 | } 229 | } 230 | 231 | // State 5: Wait for Track 4 to stop, then play three tracks sequentially and 232 | // stop all with a 5 sec fade to -50dB. This is how you can implement MIDI 233 | // Note-On/Off control for: MIDI -> Arduino -> WAV Trigger. 234 | if (gSeqState == 5) { 235 | gSeqMetro.reset(); 236 | if (!tsunami.isTrackPlaying(4)) { 237 | Serial.print("Track 4 done\n"); 238 | tsunami.masterGain(0, -8); // Lower main volume 239 | tsunami.trackPlayPoly(6, 0, true); // Play first note 240 | tsunami.update(); 241 | delay(1000); 242 | tsunami.trackPlayPoly(7, 0, true); // Play second note 243 | tsunami.update(); 244 | delay(1000); 245 | tsunami.trackPlayPoly(8, 0, true); // Play third note 246 | tsunami.update(); 247 | delay(1000); 248 | tsunami.trackFade(6, -50, 5000, true); // Fade Track 6 to -50dB and stop 249 | tsunami.trackFade(7, -50, 5000, true); // Fade Track 7 to -50dB and stop 250 | tsunami.trackFade(8, -50, 5000, true); // Fade Track 8 to -50dB and stop 251 | gSeqState = 6; 252 | } 253 | } 254 | 255 | // If time to do so, toggle the LED 256 | if (gLedMetro.check() == 1) { 257 | if (gLedState == 0) gLedState = 1; 258 | else gLedState = 0; 259 | digitalWrite(LED, gLedState); 260 | } // if (gLedMetro.check() == 1) 261 | 262 | // Delay 30 msecs 263 | delay(30); 264 | } 265 | 266 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | Tsunami KEYWORD1 2 | active KEYWORD2 3 | overflow KEYWORD2 4 | library_version KEYWORD2 5 | --------------------------------------------------------------------------------