Jump to content
43oh

larryfraz

Members
  • Content Count

    27
  • Joined

  • Last visited

Reputation Activity

  1. Like
    larryfraz got a reaction from artifus in 2-osc PWM Midi Synth   
    Link should be fixed now ...I seem to have had better luck from my DAW than the kbd...
  2. Like
    larryfraz got a reaction from abecedarian in 2-osc PWM Midi Synth   
    I put together a 2 oscillator PWM MIDI synthesizer based on NatureTM's (thanks!) 'simple synth'. No DAC is needed. The second oscillator can be detuned and has a blend control, both accessed via midi cc's. A clip of it, and the code, are on my site at the perspectivesound blog. I haven't added adsr, I may add some more features soon.
  3. Like
    larryfraz reacted to RobG in 2-osc PWM Midi Synth   
    MIDI current loop is 5mA, so 4N35 is not the best choice for MIDI application.
    On top of that, you have LED, which is in parallel with opto LED and resistor.
    If you want to see MIDI activity, connect your LED (with current limiting resistor) on the other side of opto.
     

  4. Like
    larryfraz reacted to artifus in 2-osc PWM Midi Synth   
    nice work! will be keeping an eye on this one. had a play with naturetm's simple synth a while ago - much fun. http://naturetm.com/?p=111
     
    you mention stuck notes on your blog - i had some midi communication problems when i added an opto isolator, no problems at all without it, but also wanted to stick to midi spec: http://www.midi.org/...electrispec.php which is kinda vague, not for 3.3v and i couldn't source a 6n138 or pc900 so only had a 4n35 available. once i added the opto i had all sorts of problems, erratic response, stuck notes, etc. requiring frequent resets.
     
    i fiddled with resistor values on the msp side which improved it somewhat but with still the occasional glitch. adding an led on the midi side has cured it with flawless response and no stuck notes but i've no idea as to why - if anyone can explain it would be appreciated. i had been using the led to check for midi output as here: http://www.alfors.co...estKonektor.jpg and here: http://www.epanorama...s/miditest.html due to an intermittent fault in my midi cable and stumbled upon this 'solution' by accident.
     
    anyways, here's what i ended up with (note that the 1k resistor should be 1k47):
     

     
    i used two resistors, 1k and 47ohms in series, on the msp side. might be worth playing with. i look forward to trying out your code.
     
    other links i stumbled upon when trouble shooting:
     
    http://blog.gg8.se/w..._side_of_things
    http://www.gammon.co...connections.png
  5. Like
    larryfraz got a reaction from artifus in 2-osc PWM Midi Synth   
    I put together a 2 oscillator PWM MIDI synthesizer based on NatureTM's (thanks!) 'simple synth'. No DAC is needed. The second oscillator can be detuned and has a blend control, both accessed via midi cc's. A clip of it, and the code, are on my site at the perspectivesound blog. I haven't added adsr, I may add some more features soon.
  6. Like
    larryfraz reacted to bluehash in 2-osc PWM Midi Synth   
    Thanks! and welcome to 43oh!
    Link fixed.
  7. Like
    larryfraz got a reaction from dacoffey in 2-osc PWM Midi Synth   
    I put together a 2 oscillator PWM MIDI synthesizer based on NatureTM's (thanks!) 'simple synth'. No DAC is needed. The second oscillator can be detuned and has a blend control, both accessed via midi cc's. A clip of it, and the code, are on my site at the perspectivesound blog. I haven't added adsr, I may add some more features soon.
  8. Like
    larryfraz reacted to zeke in Comprehensive DCO calibration   
    This is kind of like making the swiss army knife out of the DCO.
     
    Very useful!
    Good Job!
  9. Like
    larryfraz reacted to NatureTM in Launchpad Simple MIDI Synth   
    Update:
    This is still a work in progress, but I feel like showing some changes I've made. I wanted to make the synth polyphonic and try using a DAC, while keeping the code small enough to still fit on a G2231.
     
    For the DAC, I chose the MCP4725. I picked this one because it was the cheapest DAC-on-a-breakout from SparkFun. http://www.sparkfun.com/products/8736 I used some TI I2C example code to communicate with it.
     
    Since the I2C communications are using the USI, I needed a different way to get the serial MIDI data from the MIDI controller. I set up TimerA in capture mode, and triggered it on both rising and falling edges in the midi data. From that I could see how long the MIDI line was in a given state and thus how many 0's or 1's I got. (TimerA ticks in state / TimerA ticks per bit = number of bits.) My previous code using the USI was a mess, so I was happy to try something different.
     
    For the synth part, I have a function that takes the frequency and position within the wave, and returns an amplitude. To mix for polyphony, I just average the amplitudes of several of the waves and send the average to the DAC. The synth sounds pretty cruddy. There's several reasons for this, but it mostly boils down to a need for more free CPU time. A faster chip or more efficient code would help. I have ideas for the latter, but I may just take a completely different approach for the synth altogether. I think I'll start concentrating more on the actual sound, and less on seeing what I can squeeze from the chip. There are functions for square, triangle, and sawtooth waves, however all but square are too computationally expensive for polyphony.
     
    Here's the code -- you might want to copy/paste it into a bigger window (I really need to learn how to make a library):

    #include "msp430g2231.h" #include #define MCLK_FREQUENCY 16000000 #define MIDI_FREQUENCY 31250 #define PIN_MIDI_DATA BIT2 #define PIN_SPEAKER BIT4 #define MIDI_CHANNEL 0 // MIDI is 1-indexed so setting this to 0 is midi channel 1 #define MAX_POLYPHONY 7 #define WDT_DIVIDER 64 #define SCALE_SIZE 12 #define number_of_bytes 2 const unsigned long WDT_FREQUENCY = MCLK_FREQUENCY / WDT_DIVIDER; const unsigned int synthNotes[sCALE_SIZE] = {9956, 10548, 11175, 11840, 12544, 13290, 14080, 14917, 7902, 8372, 8869, 9397}; const unsigned int TICKS_PER_BIT = MCLK_FREQUENCY / MIDI_FREQUENCY; unsigned int wdtCounter = 0; char MST_Data[number_of_bytes] = {0x00, 0x00}; char SLV_Addr = 0xC0; char byteCount, Transmit = 0; volatile char I2C_State = 0; char newVelocity; char newNote; int noteIndex = 0; unsigned int wdtPeriodNotes[MAX_POLYPHONY]; unsigned int tNotes[MAX_POLYPHONY] = {0}; char notes[MAX_POLYPHONY] = {0}; char velocities[MAX_POLYPHONY] = {0}; bool midiRXBitState = true; char partialMidiData = 0xFF; bool haveNewMidiByte = false; char newMidiByte; void waitForMidiLoop(); void handleNoteOn(); void handleNoteOff(); void updateWaveTimes(); void synthProcess(); void mixToOutputArray(); void handleNoteEvent(); void shiftLeft(char index); void updateSynth(bool on); char getNoteIndex(char note); void addBits(unsigned int stateCount, bool state); void updateADC(void); void Setup_USI_Master_TX (void); void main(){ DCOCTL = CALDCO_16MHZ; BCSCTL1 = CALBC1_16MHZ; WDTCTL = WDTPW + WDTTMSEL + WDTIS1 + WDTIS0; IE1 |= WDTIE; TACTL |= TASSEL_2; // TACLK = SMCLK P1SEL |= PIN_MIDI_DATA; // Timer A compare input TACCTL1 |= CCIE + CAP + CM_3; // enable capture and interrupt on rising and falling edge TACCTL0 |= CCIE; // enable timeout interrupt TACCR0 = 9 * TICKS_PER_BIT; // should never have more than 8 bits P1OUT |= 0xC0; P1REN |= 0xC0; P1DIR = BIT0 + PIN_SPEAKER + BIT6 + BIT7; I2C_State = 0; // wasn't init'ing properly??? TACTL |= MC_1; // start Timer A _EINT(); // enable interrupts while(1){ waitForMidiLoop(); if(newMidiByte == (0x90 | MIDI_CHANNEL)){ haveNewMidiByte = false; handleNoteOn(); P1OUT ^= BIT0; } else if(newMidiByte == (0x80 | MIDI_CHANNEL)){ haveNewMidiByte = false; handleNoteOff(); } else haveNewMidiByte = false; } } void waitForMidiLoop(){ while(!haveNewMidiByte){ synthProcess(); } } // ################################################## // ################# Synth Stuff #################### void synthProcess(){ if(noteIndex){ updateWaveTimes(); if(haveNewMidiByte) return; mixToOutputArray(); if(haveNewMidiByte) return; // if(!I2C_State){ updateADC(); // } } } // basicly tracks our position within the waveform void updateWaveTimes(){ static unsigned int lastWdt = 0; char iNote; if(lastWdt != wdtCounter){ unsigned int wdtDelta = wdtCounter - lastWdt; lastWdt = wdtCounter; for(iNote = 0; iNote < noteIndex; iNote++){ tNotes[iNote] += wdtDelta; if(tNotes[iNote] >= wdtPeriodNotes[iNote]){ tNotes[iNote] = tNotes[iNote] - wdtPeriodNotes[iNote]; } } } } unsigned int triangle(unsigned int position, unsigned int wavelength){ unsigned int halfWavelength = wavelength / 2; if(position <= halfWavelength) return 0x0FFF * (unsigned long)position / halfWavelength; else return 0x0FFF - (0x0FFF * (unsigned long)(position - halfWavelength) / halfWavelength); } unsigned int sawtooth(unsigned int position, unsigned int wavelength){ return (unsigned long)position * 0x0FFF / wavelength; } // use this one for velocity sensitivity //unsigned int square(unsigned int position, unsigned int wavelength, char velocity){ // if(position > wavelength >> 1) // return 0x001F * velocity; // else // return 0; //} unsigned int square(unsigned int position, unsigned int wavelength){ if(position > wavelength >> 1) return 0x0FFF; else return 0; } // A fast but lo-fi digital mixer. void mixToOutputArray(){ char iSum; unsigned int sum = 0; for(iSum = 0; iSum < noteIndex; iSum++) sum += square(tNotes[iSum], wdtPeriodNotes[iSum]); // using waveforms other than square takes extra cpu time and can lead to unexpected results, // but they're there to mess with. (you may miss midi events and get crudd(y/ier) sound) // sum += triangle(tNotes[iSum], wdtPeriodNotes[iSum]); // sum += sawtooth(tNotes[iSum], wdtPeriodNotes[iSum]); // use this one for velocity sensitivity // sum += square(tNotes[iSum], wdtPeriodNotes[iSum], velocities[iSum]); // Hack to keep the volume more level if(noteIndex == 1) sum /= 2; else sum /= noteIndex; MST_Data[0] = 0x000F & (sum >> 8); MST_Data[1] = 0x00FF & sum; } unsigned int midiNoteToWdtPeriod(char midiNote){ return (WDT_FREQUENCY / (synthNotes[midiNote % 12] >> ((127 - midiNote) / 12))); } #pragma vector=WDT_VECTOR __interrupt void watchdog_timer(void){ wdtCounter++; } // ############################################### // ################# MIDI stuff ################## void handleNoteOn(){ do{ waitForMidiLoop(); if(newMidiByte & 0x80) break; newNote = newMidiByte; haveNewMidiByte = false; waitForMidiLoop(); if(newMidiByte & 0x80) break; newVelocity = newMidiByte; haveNewMidiByte = false; handleNoteEvent(); }while(1); } void handleNoteOff(){ do{ waitForMidiLoop(); if(newMidiByte & 0x80) break; newNote = newMidiByte; haveNewMidiByte = false; newVelocity = 0; handleNoteEvent(); }while(1); } void handleNoteEvent(){ if(newVelocity != 0){ // add a new note when the poly cache is full, replacing the oldest if(MAX_POLYPHONY == noteIndex){ shiftLeft(0); notes[MAX_POLYPHONY - 1] = newNote; velocities[MAX_POLYPHONY - 1] = newVelocity; wdtPeriodNotes[MAX_POLYPHONY - 1] = midiNoteToWdtPeriod(newNote); } // add a new note else{ notes[noteIndex] = newNote; velocities[noteIndex] = newVelocity; wdtPeriodNotes[noteIndex] = midiNoteToWdtPeriod(newNote); noteIndex++; } } else if(getNoteIndex(newNote) < MAX_POLYPHONY){ shiftLeft(getNoteIndex(newNote)); noteIndex -= 2; if(noteIndex >= 0){ noteIndex++; } else{ noteIndex = 0; // set DC offset to zero when no notes MST_Data[0] = 0; MST_Data[1] = 0; updateADC(); } } } // Shift all notes to the right of index left by one, // overwriting index and freeing a spot at the end of // the array void shiftLeft(char index){ int i; for(i = index; i < MAX_POLYPHONY - 1; i++){ notes[i] = notes[i + 1]; velocities[i] = velocities[i + 1]; wdtPeriodNotes[i] = wdtPeriodNotes[i + 1]; } } char getNoteIndex(char note){ int i; for(i = 0; i < MAX_POLYPHONY; i++) if(notes[i] == note) return i; return MAX_POLYPHONY + 1; } // add a new bit to the incomplete midi data variable void shiftInMSB(bool state){ if(state){ partialMidiData >>= 1; partialMidiData |= BIT7; } else{ partialMidiData >>= 1; } } // add the new midi data to the incomplete midi data variable void addBits(unsigned int stateCount, bool state){ while(stateCount > TICKS_PER_BIT / 2){ if(!(partialMidiData & BIT0)){ // are we shifting out the start bit? // if(haveNewMidiByte) // while(1); // catch when we miss midi data (for debugging) shiftInMSB(state); newMidiByte = partialMidiData; // if((newMidiByte != 0xFE) && (newMidiByte != 0xFF)) if(newMidiByte != 0xFE) haveNewMidiByte = true; partialMidiData = 0xFF; } else shiftInMSB(state); stateCount -= TICKS_PER_BIT; } } // CCR1 capture interrupt. Triggers on both edges, adds fresh midi data #pragma vector=TIMERA1_VECTOR __interrupt void CCR1_interrupt(void){ TAR = 0; TACCTL0 &= ~CCIFG; unsigned int tEdge = TACCR1; midiRXBitState = PIN_MIDI_DATA & P1IN; TAIV = 0; addBits(tEdge, !midiRXBitState); } // Like a timeout to add midi data if there's new data, but no edge in too long #pragma vector=TIMERA0_VECTOR __interrupt void CCR0_interrupt(void){ // don't add data if no partial data received, unless the new is all 0's if((partialMidiData != 0xFF) || !midiRXBitState) addBits(9 * TICKS_PER_BIT, midiRXBitState); // in case we captured an edge during this ISR, don't re-add these bits // in the CCR1 ISR TACCR1 = 0; } // ############################################################ // ###################### I2C ADC Stuff ####################### void updateADC(void){ Setup_USI_Master_TX(); USICTL1 |= USIIFG; // Set flag and start communication } void Data_TX (void){ USISRL = MST_Data[byteCount]; // Load data byte USICNT |= 0x08; // Bit counter = 8, start TX I2C_State = 10; // next state: receive data (N)Ack byteCount++; } void Setup_USI_Master_TX (void) { _DINT(); byteCount = 0; Transmit = 1; USICTL0 = USIPE6+USIPE7+USIMST+USISWRST; // Port & USI mode setup USICTL1 = USII2C+USIIE; // Enable I2C mode & USI interrupt USICKCTL = USIDIV_3+USISSEL_2+USICKPL; // USI clk: SCL = SMCLK/128 USICNT |= USIIFGCC; // Disable automatic clear control USICTL0 &= ~USISWRST; // Enable USI USICTL1 &= ~USIIFG; // Clear pending flag _EINT(); } // I2C handler for ADC adapted from TI example code. This runs pretty quick, // so hopefully it doen't clash with Midi input interrupts. #pragma vector = USI_VECTOR __interrupt void USI_TXRX (void) { switch(__even_in_range(I2C_State,14)) { case 0: // Generate Start Condition & send address to slave byteCount = 0; USISRL = 0x00; // Generate Start Condition... USICTL0 |= USIGE+USIOE; USICTL0 &= ~USIGE; USISRL = SLV_Addr; USICNT = (USICNT & 0xE0) + 0x08; // Bit counter = 8, TX Address I2C_State = 2; // next state: rcv address (N)Ack break; case 2: // Receive Address Ack/Nack bit USICTL0 &= ~USIOE; // SDA = input USICNT |= 0x01; // Bit counter=1, receive (N)Ack bit I2C_State = 4; // Go to next state: check (N)Ack break; case 4: // Process Address Ack/Nack & handle data TX USICTL0 |= USIOE; // SDA = output if (USISRL & 0x01) // If Nack received... { // Send stop... USISRL = 0x00; USICNT |= 0x01; // Bit counter=1, SCL high, SDA low I2C_State = 14; // Go to next state: generate Stop } else { // Ack received, TX data to slave... USISRL = MST_Data[byteCount]; // Load data byte USICNT |= 0x08; // Bit counter = 8, start TX I2C_State = 10; // next state: receive data (N)Ack byteCount++; } break; case 10: // Receive Data Ack/Nack bit USICTL0 &= ~USIOE; // SDA = input USICNT |= 0x01; // Bit counter = 1, receive (N)Ack bit I2C_State = 12; // Go to next state: check (N)Ack break; case 12: // Process Data Ack/Nack & send Stop USICTL0 |= USIOE; if (byteCount == number_of_bytes){// If last byte USISRL = 0x00; I2C_State = 14; // Go to next state: generate Stop USICNT |= 0x01; } // set count=1 to trigger next state else{ Data_TX(); // TX byte } break; case 14:// Generate Stop Condition USISRL = 0x0FF; // USISRL = 1 to release SDA USICTL0 |= USIGE; // Transparent latch enabled USICTL0 &= ~(USIGE+USIOE); // Latch/SDA output disabled I2C_State = 0; // Reset state machine for next xmt break; } USICTL1 &= ~USIIFG; // Clear pending flag }
     


     
    I was planning on entering this in the POTM, but it sounds so bad! My cats start meowing and scratching at the door when I play it! I think I will enter the original version, however.
     

    NOTE: There should be about a 190K resistor between the DAC and the amp in the schematic. Not entirely necessary, but will scale the DAC output down to something more reasonable for the LM386
     

     

     

     

     

×
×
  • Create New...