Jump to content
43oh

Generative Synthesis - Laser Gun Sounds?


Recommended Posts

Hi,

 

Since I have two of the MSP430's left over from the two launchpads I bought, I though I'd breathe new life and fun into the now-obsolete PlayStation one light guns. Using an MS430, an 8-pin DAC in a DIP package (an MCP4911) and an LM386 for amplification.

 

Thus far, the basic plan is:

  1. Run the 430 of two AAs directly
  2. Use a DC converter (TPS61041) to step this up to 5 volts ish for the LM386, Use a FET to turn this 5v side off when in sleep mode
  3. Use the 430 to generate some incredibly awesome laser gun sound...
  4. ..and actually a cool reloading (yes I know laser guns don't reload) sound too, because the gun has these cool buttons on the sides too.

Steps one and two are no problem, and talking to the DAC is over SPI so that should be straightforwards too. This should be a fun project, but I've never done any synthesis before. I guess I could put an SD card in there and use digital samples, but I'd like to create the sounds generatively. 

 

Has anyone got any ideas? I've heard of FM synthesis, but it doesn't sound all that flash to me. Also, I've come across this rather amazing thing, which might also be a possibility if I could get the numbers right:

 

http://countercomplex.blogspot.co.nz/2011/10/algorithmic-symphonies-from-one-line-of.html

 

And just to add a picture too, here's the surface-mount DC converter soldered onto a bit of perfboard next to the schottky diode.

 

post-31167-0-35357600-1360744882_thumb.jpg

Link to post
Share on other sites

Keep in mind that alkaline cells (AAA, AA, C, D, etc) begin at 1.5V and have an almost linear decline to 0.9V over their useful life.  The MSP430 can run on 1.8V, but only at 6 MHz. It requires at least 3.3V to run at 16 MHz.

 

Here is a simple laser sound using an exponential change in frequency of a square wave on P1.6.

 

#include <msp430.h>

void main(void)
{
    WDTCTL = WDTPW | WDTHOLD;                   // Disable watchdog
                                                //
    DCOCTL = 0;                                 // Run DCO at 16 MHz
    BCSCTL1 = CALBC1_16MHZ;                     //
    DCOCTL  = CALDCO_16MHZ;                     //
                                                //
                                                // I/O assignment
    P1DIR = BIT7 | BIT6 | BIT5 | BIT4 | BIT2 | BIT0; // Direction
    P1REN = BIT3;                               // Resistors
    P1OUT = BIT2;                               // Output
    P1SEL = BIT6 | BIT4 | BIT1;                 // Select Timer A output, SMCLK output, UART Rx
                                                //
    TA0CTL = TASSEL_2 | MC_1;                   // Timer A config: SMCLK, count up
    TA0CCR1 = 1;                                //
                                                //
    for(; {                                   // Forever
        unsigned n;                             //
        TA0CCTL1 = OUTMOD_4;                    // Timer out mode toggle
        //for(n = 200; n < 7000; ++n) {         // High -> low freq
        for(n = 7000; n > 200; --n) {           // Low -> high freq
            TA0CCR0 = n;                        // Set timer period
            __delay_cycles(200);                // Rate of frequency change
        }                                       //
        TA0CCTL1 = OUTMOD_0;                    // Timer out mode off
        __delay_cycles(16000000L * 2);          // Wait 2 seconds
    }                                           //
}
 

 

Here is the "algorithmic symphonies" using PWM audio on P1.6. Only three formulas in this code - just enough to test it.

 

/*
    Copyright © 2012  Kevin Timmerman

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <msp430.h>
#include <stdint.h>

static volatile unsigned sample = 244;          // PWM sample
static volatile unsigned s = 0;                 // Timer

void main(void)
{
    WDTCTL = WDTPW | WDTHOLD;                   // Disable watchdog
                                                //
    DCOCTL = 0;                                 // Run DCO at 16 MHz
    BCSCTL1 = CALBC1_16MHZ;                     //
    DCOCTL  = CALDCO_16MHZ;                     //
                                                //
                                                // I/O assignment
    P1DIR = BIT7 | BIT6 | BIT5 | BIT4 | BIT2 | BIT0; // Direction
    P1REN = BIT3;                               // Resistors
    P1OUT = BIT2;                               // Output
    P1SEL = BIT6 | BIT4 | BIT1;                 // Select Timer A output, SMCLK output, UART Rx
                                                //
    TA0CCR0 = (16000000L / 32768L) - 1;         // Setup Timer A for 32768 Hz period
    TA0CCR1  = TACCR0 / 2;                      // Setup Timer A compare to midpoint
    TA0CCTL1 = OUTMOD_7;                        // Setup Timer A reset/set output mode
    TA0CTL = TASSEL_2 | MC_1;                   // Timer A config: SMCLK, count up
    TA0CCTL0 |= CCIE;                           // Enable period interrupt
                                                //
    _enable_interrupts();                       // Enable interrupts
                                                //
    unsigned n = 0;                             //                                              
    unsigned x = 244;                           //
    for(; {                                   // Forever
        long int t;                             //
        long l = 8192L * 10;                    // Track length
        for(t = 0; --l; t++) {                  //
            s = 4;                              // 4 PWM pulses per PCM sample
            sample = x;                         // Update PCM sample
            x = 116;                            // Set PWM bias
            switch(n) {                         // Calculate next sample
                default:                        //
                    n = 0;                      // Start over
                case 0:                         //
                    x += (char)(t * (t >> 12 | t >> 8) & 63 & t >> 4);
                    break;                      //
                case 1:                         //
                    x += (char)((t * (t >> 5 | t >> 8)) >> (t >> 16));
                    break;                      //
                case 2:                         //
                    x += (char)(t * ((t >> 9 | t >> 13) & 25 & t >> 6));
                    break;                      //
            }                                   //
            __bis_SR_register(LPM0_bits + GIE); // Wait for PCM sample period to end
        }                                       //
        ++n;                                    // Next track
        __delay_cycles(16000000L * 2);          // Wait 2 seconds
    }                                           //
}

#pragma vector = TIMER0_A0_VECTOR               // Timer A CCR0 interrupt
__interrupt void timer_a0_isr(void)             // This interrupt will occur when the PWM period is near complete
                                                //   when TAR == CCR0 - 1
{                                               //
                                                //
    TA0CCR1 = sample;                           // Output sample
                                                //
    if(s) if(!--s) __bic_SR_register_on_exit(LPM0_bits);
}                                               //

Link to post
Share on other sites

That's a very good point about the supply voltage. I'll have to run some experiments to see how much speed I'll need to generate good sound effects. 6Mhz might well be enough because I'll be sending samples to a DAC at probably only 20kHz or so. At 16 bits per sample (even though it's only a 10 bit dac, it still wants 16 bits before it'll output...) that's about 320kHz, or 0.3Mhz SPI clock speed.

 

So from that point of view 6Mhz would be plenty to drive the DAC. Then it's just a question of writing some really tight code with a lookup table / algorithmic outputs and seeing what comes out.

Link to post
Share on other sites

So, with an eye to the exponential chirp code above, and following some-one else's advice in youtube, I've got a phased linear chirp plus some filtered white noise sounding quite nice in matlab (m-file attached for those who happen to have access to matlab and are interested laser.m.txt). 

 

I'm probably going to need to code this in assembly in order to keep up with the 20kHz ouput frequency, since at 8Mhz I'll have only 400 cycles to calculate each sample.

 

Actually, that's probably plenty, but assembly is interesting anyway, and presumably there are some great resources on assembly on this site somewhere. I don't need a basic introduction, just a nice table of the assembly instructions and what they do (different kinds of shifts, how the flags are effected, addressing modes, etc etc).

 

If anyone could point me in that direction, I would be extremely grateful, and will of course post the final code that I come up with.

 

Thanks very much in advance.

 

-dave

Link to post
Share on other sites

Wikipedia has a nice table of instructions: http://en.wikipedia.org/wiki/TI_MSP430#MSP430_CPU

 

Details of the instruction set are in the 2000 series users guide: http://www.ti.com/litv/pdf/slau144i

 

An example of using C & asm for sound synthesis: http://forum.43oh.com/topic/912-32k-interupts-a-second/?p=10283

 

Details of using assembly: http://www.ti.com/litv/pdf/slau132g and http://www.ti.com/litv/pdf/slau131e

Basically R12 to R15 are passed from C code and you can trash them - preserve all others.

 

The multiplications in your code (in the IIR filter) will take by far the most time because the 2000 series does not have a hardware multiplier,.

Link to post
Share on other sites

Thanks for the hint about the multiplier, I decomposed the multiplies into various shifts and adds, and the c version of the code works great!

 

...So I thought to myself, even though I don't actually need to, I'll have a crack at using the inline assembly to get the same results and free up some cycles. 

 

...But... inline assembly is a strange beast in msp430-gcc land. I honestly can't make head nor tail of the documentation, and very strange things started to happen. But in any case, I did have a question about this code:

 

	mov	&fade_shifts_counter, r15
	add	#llo(-1), r15
	mov	r15, &fade_shifts_counter
	mov	&fade_shifts_counter, r15
	cmp	#0, r15
	jne	.L5
	mov.b	&fade_shifts, r15
	add.b	#1, r15
	mov.b	r15, &fade_shifts
	mov	#2000, &fade_shifts_counter
.L5:

 

This is what msp430-gcc produces in response to the following c code

fade_shifts_counter--;
if (fade_shifts_counter == 0)
{
  fade_shifts++;
  fade_shifts_counter = FADE_SHIFTS_STEP;
}

 

Now, to me the assembly looks pretty strange, and very poorly optimised. My attempt at hand-coding the assembly gives:

	dec.w &fade_shifts_counter
	jnz	.CounterNotZero
	inc.b &fade_shifts
	mov.w #2000,&fade_shifts_counter
 .CounterNotZero:"

 

Which seems like it should perform a bit better and doesn't clobber any registers, but doesn't appear to actually work. Or rather, appears to behave in strange ways that I haven't quite got to the bottom of yet. I rather suspect that this may be related to the strange syntax employed by msp430-gcc for inline assembly. This document http://mspgcc.sourceforge.net/manual/c1308.html describes the inline assembly construct that I'm struggling with.

 

On the plus side, my synthesiser is working, and sounds pretty cool, and looks like it should run from two AA's (although at the moment it doesn't, which seems to be something to do with the boost circuit - the msp430 is perfectly happy). And soon I'll squeeze it onto some perfboard and stick it into one of the light guns.

Link to post
Share on other sites

But... inline assembly is a strange beast in msp430-gcc land. I honestly can't make head nor tail of the documentation, and very strange things started to happen.

 

 

It is. One of many reasons I use CCS - easy to mix asm with C, better documentation, usually faster code, and overall just much more productive.

 

 

But in any case, I did have a question about this code:

 

 

Try this,  Think in asm, write in C.

 

if(--fade_shifts_counter)
{
  fade_shifts_counter = FADE_SHIFTS_STEP;
  ++fade_shift;
}
Link to post
Share on other sites

Well I ran the same code through CCS, and it's a damn clever compiler. It even figured out that my sequence of shifts and adds constituted a multiply and jumped off into some subroutine apparently called __mspabi_mpyi. Impressive - assuming that this would be faster than the shifts and adds of course, but I'll give it the benefit of the doubt.

 

So I'll be sticking with CCS, even though I'll have to run it in a VM it's going to save me a lot of time. Thanks very much for your advice.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...