Exemplo n.º 1
0
// find a note in the array of MAX_NOTES available notes to use
byte findNoteIndex() {
  for(byte i=0;i<MAX_NOTES;i++) {
    // find a slot for this note
    if (note[i].midiVal <= NOTE_PENDING_OFF) {
      while ((note[i].midiVal != NOTE_OFF) && ((note[i].phaseInc > 0) || (note[i].phaseFractionInc > 0))) {
	// wait until note reaches NOTE_OFF state to avoid click
      }
      return i;
    }
  }

  // There are no notes that are not being used. Check to see if any
  // are in RELEASE envelope phase and use it. Find the oldest of the
  // notes in RELEASE phase.
  byte noteIndex = 255;
  unsigned long oldest = millis();
  for(byte i=0;i<MAX_NOTES;i++) {
    if ((note[i].envelopePhase == RELEASE) && (!note[i].isSample) && (note[i].startTime < oldest)) {
      noteIndex = i;
      oldest = note[i].startTime;
    }
  }
  if (noteIndex != 255) {
    stopNote(noteIndex);
    return noteIndex;
  }


  // All notes are being used. Choose the note that is "oldest".
  // Start by looking for non-sample notes.
  noteIndex = 255;
  for(byte i=0;i<MAX_NOTES;i++) {
    if (!note[i].isSample) {
      noteIndex = i;
      break;
    }
  }
  if (noteIndex != 255) {
    for(byte i=0;i<MAX_NOTES;i++) {
      if ((!note[i].isSample) && (note[i].startTime < note[noteIndex].startTime)) {
	// try to find an older nonsample note.
	noteIndex = i;
      }
    }
  } else {
    // All notes are samples. Now try to find the oldest.
    noteIndex = 0;
    for(byte i=1;i<MAX_NOTES;i++) {
      if (note[i].startTime < note[noteIndex].startTime) {
	noteIndex = i;
      }
    }
  }
  // At this point noteIndex is the oldest note, giving priority to samples.

  if (note[noteIndex].phaseInc > 0) {
    stopNote(noteIndex);
  }
  return noteIndex;
}
Exemplo n.º 2
0
void ArduboyTunes::initChannel(byte pin)
{
  byte timer_num;

  // we are all out of timers
  if (_tune_num_chans == AVAILABLE_TIMERS)
    return;

  timer_num = pgm_read_byte(tune_pin_to_timer_PGM + _tune_num_chans);
  _tune_pins[_tune_num_chans] = pin;
  _tune_num_chans++;
  pinMode(pin, OUTPUT);
  switch (timer_num) {
    case 1: // 16 bit timer
      TCCR1A = 0;
      TCCR1B = 0;
      bitWrite(TCCR1B, WGM12, 1);
      bitWrite(TCCR1B, CS10, 1);
      _tunes_timer1_pin_port = portOutputRegister(digitalPinToPort(pin));
      _tunes_timer1_pin_mask = digitalPinToBitMask(pin);
      break;
    case 3: // 16 bit timer
      TCCR3A = 0;
      TCCR3B = 0;
      bitWrite(TCCR3B, WGM32, 1);
      bitWrite(TCCR3B, CS30, 1);
      _tunes_timer3_pin_port = portOutputRegister(digitalPinToPort(pin));
      _tunes_timer3_pin_mask = digitalPinToBitMask(pin);
      playNote(0, 60);  /* start and stop channel 0 (timer 3) on middle C so wait/delay works */
      stopNote(0);
      break;
  }
}
Exemplo n.º 3
0
/* if CMD < 0x80, then the other 7 bits and the next byte are a 15-bit big-endian number of msec to wait */
void ArduboyTunes::step()
{
  byte command, opcode, chan;
  unsigned duration;

  while (1) {
    command = pgm_read_byte(score_cursor++);
    opcode = command & 0xf0;
    chan = command & 0x0f;
    if (opcode == TUNE_OP_STOPNOTE) { /* stop note */
      stopNote(chan);
    }
    else if (opcode == TUNE_OP_PLAYNOTE) { /* play note */
      playNote(chan, pgm_read_byte(score_cursor++));
    }
    else if (opcode == TUNE_OP_RESTART) { /* restart score */
      score_cursor = score_start;
    }
    else if (opcode == TUNE_OP_STOP) { /* stop score */
      tune_playing = false;
      break;
    }
    else if (opcode < 0x80) { /* wait count in msec. */
      duration = ((unsigned)command << 8) | (pgm_read_byte(score_cursor++));
      wait_toggle_count = ((unsigned long) wait_timer_frequency2 * duration + 500) / 1000;
      if (wait_toggle_count == 0) wait_toggle_count = 1;
      break;
    }
  }
}
Exemplo n.º 4
0
void stopSequencer() {
  // if any notes are on stop them
  for(byte t=0;t<SEQ_NUM_TRACKS;t++) {
    sequenceNote_t *sn = &seq[track[t].noteSeqIndex][t];
    if ((note[sn->noteIndex].envelopePhase != OFF)) {
      stopNote(sn->noteIndex);
      note[sn->noteIndex].trigger = UNSET;
    }
  }

  if (metronomeNoteIndex != UNSET) {
    stopNote(metronomeNoteIndex);
    metronomeNoteIndex = UNSET;
  }

  digitalWrite(led[seqLEDIndex], LOW);
  ledState[seqLEDIndex] = LOW;
  ledState[PREVIEW_LED] = LOW;
  seqPreview = false;
}
Exemplo n.º 5
0
void readGrooveboxButtons() {
  if (buttonPressedNew(BUTTON1) && (!buttonPressed(BUTTON4))) {
    // start/stop live sequencer groovebox
    if (!seqRunning) {
      resetGroovebox();
      seqSynchStart = true;
      while ((buttonHeld(BUTTON1)) && (seqSynchStart)) {
	// wait until the button is released or until a MIDI event triggers the start
	if (buttonPressed(BUTTON4)) {
	  // save procedure
	  return;
	}
#ifdef MIDI_ENABLE
	MIDI.read();
#endif
#ifdef USBMIDI_ENABLE
	USBMIDI.read();
#endif
      }
    }
    seqSynchStart = false;
    seqRunning = !seqRunning;
    if (!seqRunning) {
      stopSequencer();
    }
  }

  if (buttonPressedNew(BUTTON2)) {
    // clear the sequence notes on this track.
    for(byte i=0;i<SEQ_LENGTH;i++) {
      seq[i][currentTrack].midiVal = UNSET;
      seq[i][currentTrack].transpose = 0;
    }
  }

  if ((buttonPressedNew(BUTTON3)) && (seqRunning)) {
    seqPreview = !seqPreview;
  }
  if (seqPreview) {
    ledState[PREVIEW_LED] = HIGH;
  }

  if (buttonPressedNew(BUTTON4)) {
    metronomeMode = (metronomeMode + 1) % 3;
    if ((metronomeMode == METRONOME_MODE_OFF) && (metronomeNoteIndex != UNSET)) {
      stopNote(metronomeNoteIndex);
      metronomeNoteIndex = UNSET;
    }
  }


}
Exemplo n.º 6
0
void grooveboxNoteOn(byte channelNum, byte midiNote, byte velocity) {
  seqLock = true;
  // snap to nearest sequencer position
  byte seqSnapIndex;
  int noteStart = pulseClock;
  // noteStartOffset is the timing error. It is the difference between when the note
  // was pressed and the nearest sequencer note boundary.
  int noteStartOffset = (pulseClock % seqStartPulse);
  boolean snappedForward = false;
  if ((noteStartOffset > 0) && (noteStartOffset <= (seqStartPulse/2))) {
    // snap backward
    seqSnapIndex = seqIndex;
  } else {
    // snap forward
    seqSnapIndex = (seqIndex + 1) % SEQ_LENGTH;
    snappedForward = true;
  }
  if (seqSynchStart) {
    seqSnapIndex = 0;
  }

  sequenceNote_t *sn;
  note_t *n;
  byte currentNoteSeqIndex = track[currentTrack].noteSeqIndex;
  sn = &seq[currentNoteSeqIndex][currentTrack];
  n = &note[sn->noteIndex];
  if (n->trigger == currentTrack) {
    if ((n->envelopePhase != OFF) || (n->isSample)) {
      // A note on the current track is playing.
      if ((!seqPreview) && (seqRunning) && (n->envelopePhase != RELEASE)) {
	// If we are not previewing and sequence is running, and it's
	// not in RELEASE phase yet, adjust its duration and stop the note.
	if ((sn->duration == 0) && (currentNoteSeqIndex != seqSnapIndex)) {
	  // If the note being played has not been released yet, set a duration for it.
	  unsigned int newDuration;
	  if ((snappedForward) && (noteStartOffset > 0)) {
	    newDuration = (noteStart + (seqStartPulse-noteStartOffset)) - sn->startPulse;
	  } else {
	    newDuration = (noteStart - noteStartOffset) - sn->startPulse;
	  }
	  debugprint("setting duration of note ", currentNoteSeqIndex);
	  debugprintln(" to ", newDuration);
	  sn->duration = newDuration;
	}
      }
      stopNote(sn->noteIndex);
      note[sn->noteIndex].trigger = UNSET;
    }
  }

  byte ni = doNoteOn(selectedSettings+1, midiNote, velocity);
  if (channelNum == 10) {
    setDrumParameters(ni, midiNote, velocity);
  }
  note[ni].volumeScale = track[currentTrack].volumeScale;
  if (note[ni].volumeScale != 1.0) {
    if (note[ni].isSample) {
      note[ni].doScale = true;
    }
  }
  note[ni].isPreview = seqPreview;
  if ((!seqPreview) && (seqRunning || seqSynchStart)) {
    sn = &seq[seqSnapIndex][currentTrack];
    sn->noteIndex = ni;
    track[currentTrack].noteSeqIndex = seqSnapIndex;
    
    note[sn->noteIndex].trigger = currentTrack;
    sn->startPulse = noteStart;
    sn->midiVal = midiNote;
    if (channelNum == 10) {
      sn->midiVal = sn->midiVal | 0x80; // set high bit to indicate ch10
    }
    sn->transpose = 0;
    track[currentTrack].state = SEQ_RECORD;
    sn->velocity = velocity;
    sn->duration = 0;
    sn->waveform = note[ni].waveform;
  }
  if (seqSynchStart) seqSynchStart = false;
  seqLock = false;
}
Exemplo n.º 7
0
void doGroovebox() {
  if (!seqRunning) return;
  unsigned int duration;

  sequenceNote_t *sn;
  for(byte t=0;t<SEQ_NUM_TRACKS;t++) {
    sn = &seq[track[t].noteSeqIndex][t];
    note_t *n = &note[sn->noteIndex];
    if (track[t].state == SEQ_PLAY) {
      // check to see if this note's duration is elapsed, but only
      // if it has not been released.
      if ((n->trigger == t) && (n->envelopePhase != RELEASE) && (n->envelopePhase != OFF)) {
	duration = pulseClock - sn->startPulse;
	if (duration >= sn->duration) {
	  // time to release this note
	  if (duration != sn->duration) {
	    debugprintln("track=", t);
	    debugprintln("noteSeqIndex=", track[t].noteSeqIndex);
	    debugprint("ERROR: releasing note with duration ", sn->duration);
	    debugprintln(" at computed duration ", duration);
	    debugprintln("env=", n->envelopePhase);
	    sn->duration = duration;
	  }
	  releaseNote(n);
	}
      }
    }
  }

  if ((pulseClock % seqStartPulse) == 0) {
    sequenceNote_t *sn;
    sequenceNote_t *nextsn;
    seqIndex = (seqIndex + 1) % SEQ_LENGTH;
    if (seqIndex < 10) {
      debugprint(" ", seqIndex);
    } else {
      debugprint("", seqIndex);
    }
    debugprint(":");
  
    seqLEDIndex = seqIndex / (SEQ_LENGTH / 4);
    digitalWrite(led[seqLEDIndex], HIGH);
    ledState[seqLEDIndex] = HIGH;

    // The play/record head has reached a note boundary.
    for(byte t=0;t<SEQ_NUM_TRACKS;t++) {
      sn = &seq[track[t].noteSeqIndex][t];
      note_t *n = &note[sn->noteIndex];
      if (track[t].state == SEQ_PLAY) {
	// Check if it is time to start a new note at this position in the sequence.
	nextsn = &seq[seqIndex][t];
	if (nextsn->midiVal != UNSET) {
	  // There is a note here in the sequence to play
	  // See if there is a currently playing note and stop it.
	  if (n->trigger == t) {
	    if ((n->envelopePhase != RELEASE) && (n->envelopePhase != OFF)) {
	      // A currently playing note should have been released above
	      // because its duration should have elapsed.
	      debugprint(" adjusting note ", track[t].noteSeqIndex);
	      debugprint(" duration from  ", sn->duration);
	      debugprintln(" to ", pulseClock - sn->startPulse);
	      sn->duration = pulseClock - sn->startPulse;
	    }
	    // Stop the note
	    stopNote(sn->noteIndex);
	    n->trigger = UNSET;
	  }

	  if ((pulseClock > nextsn->startPulse) && ((pulseClock - nextsn->startPulse) < seqStartPulse)) {
	    // avoid double hit for short note that was already released but snapped to this position
	    debugprintln("avoiding double hit: ", pulseClock-nextsn->startPulse);
	  } else {
	    byte midiVal = (nextsn->midiVal & 0x7F);
	    boolean isDrumChannel = (nextsn->midiVal) & 0x80; // high bit indicates ch10
	    if (nextsn->waveform < N_WAVEFORMS) {
	      // transpose notes that are not samples
	      midiVal = constrain(nextsn->midiVal + nextsn->transpose, MIDI_LOW, MIDI_HIGH);
	    }
	    nextsn->noteIndex = doNoteOn(t+1, midiVal, nextsn->velocity);
	    if (isDrumChannel) {
	      setDrumParameters(nextsn->noteIndex, midiVal, nextsn->velocity);
	    }
	    note[nextsn->noteIndex].trigger = t;
	    nextsn->startPulse = pulseClock;
	    track[t].noteSeqIndex = seqIndex;
	    // Set the waveform to the one that was recorded.
	    // This only works if we don't call setPhaseIncrement() in doNoteOn()
	    if (!isDrumChannel) {
	      note[nextsn->noteIndex].waveformBuf = waveformBuffers[nextsn->waveform];
	    }
	    if (nextsn->waveform >= N_WAVEFORMS) {
	      note[nextsn->noteIndex].isSample = true;
	      if (!isDrumChannel) {
		// already taken care of in setDrumParameters().
		note[nextsn->noteIndex].sampleLength = sampleLength[nextsn->waveform - N_WAVEFORMS];
		note[nextsn->noteIndex].volIndex = note[nextsn->noteIndex].targetVolIndex;
		note[nextsn->noteIndex].volume = logVolume[note[nextsn->noteIndex].volIndex];
		note[nextsn->noteIndex].volumeNext = note[nextsn->noteIndex].volume;
	      }
	    } else {
	      note[nextsn->noteIndex].isSample = false;
	    }

	    note[nextsn->noteIndex].volumeScale = track[t].volumeScale;
	    if (note[nextsn->noteIndex].volumeScale != 1.0) {
	      if (note[nextsn->noteIndex].isSample) {
		note[nextsn->noteIndex].doScale = true;
	      }
	    }
	  }
	}
      } // track in SEQ_PLAY state


      if (track[t].state == SEQ_RECORD) {
	// a note is currently being held and recorded
	// check to see if there is a note here in the sequence 
	// and if so, overwrite it.
	if ((seq[seqIndex][t].midiVal != UNSET) && (seqIndex != track[t].noteSeqIndex)) {
	  // Only clear the note if it's not the one being recorded.
	  // I may be the one being recorded because the recording started before
	  // the pulseClock reached this point in the sequence and the new note 
	  // "snapped" to this location.
	  seq[seqIndex][t].midiVal = UNSET;
	  seq[seqIndex][t].transpose = 0;
	  // Note that this may not actually be the right thing to do because
	  // the currently recorded note might be released right after the play
	  // head passes this point in the sequence, and the release snaps back to this
	  // point. If that were the case, we'd want to keep the note that is already
	  // here.
	}
      }

      if (seq[seqIndex][t].midiVal != UNSET) {
	debugprint(" ", (seq[seqIndex][t].midiVal & 0x7F));
	if (seq[seqIndex][t].duration > 0) {
	  debugprint("[", seq[seqIndex][t].duration);
	} else {
	  debugprint("[UNKNOWN");
	}
	debugprint("]");
      } else {
	debugprint("   ");
      }
      if (t == (SEQ_NUM_TRACKS-1)) {
	debugprintln("");
      }
    } // for each track

    if (metronomeMode != METRONOME_MODE_OFF) {
      byte tickFrequency;
      if (metronomeMode == METRONOME_MODE_QUARTER) {
	tickFrequency = 4;
      }
      if (metronomeMode == METRONOME_MODE_SIXTEENTH) {
	tickFrequency = 1;
      }
      if ((seqIndex % tickFrequency) == 0) {
	if (seqIndex == 0) {
	  metronomeNoteIndex = metronomeTick(64, MAX_NOTE_VOL);
	} else {
	  metronomeNoteIndex = metronomeTick(60, 40);
	}
      }
    }
  }



  if (((pulseClock - (seqStartPulse/2)) % seqStartPulse) == 0) {
    digitalWrite(led[seqLEDIndex], LOW);
    ledState[seqLEDIndex] = LOW;
  }
}
Exemplo n.º 8
0
//==============================================================================
void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples)
{
    if (const SamplerSound* const playingSound = static_cast <SamplerSound*> (getCurrentlyPlayingSound().get()))
    {
        const float* const inL = playingSound->data->getSampleData (0, 0);
        const float* const inR = playingSound->data->getNumChannels() > 1
                                    ? playingSound->data->getSampleData (1, 0) : nullptr;

        float* outL = outputBuffer.getSampleData (0, startSample);
        float* outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getSampleData (1, startSample) : nullptr;

        while (--numSamples >= 0)
        {
            const int pos = (int) sourceSamplePosition;
            const float alpha = (float) (sourceSamplePosition - pos);
            const float invAlpha = 1.0f - alpha;

            // just using a very simple linear interpolation here..
            float l = (inL [pos] * invAlpha + inL [pos + 1] * alpha);
            float r = (inR != nullptr) ? (inR [pos] * invAlpha + inR [pos + 1] * alpha)
                                       : l;

            l *= lgain;
            r *= rgain;

            if (isInAttack)
            {
                l *= attackReleaseLevel;
                r *= attackReleaseLevel;

                attackReleaseLevel += attackDelta;

                if (attackReleaseLevel >= 1.0f)
                {
                    attackReleaseLevel = 1.0f;
                    isInAttack = false;
                }
            }
            else if (isInRelease)
            {
                l *= attackReleaseLevel;
                r *= attackReleaseLevel;

                attackReleaseLevel += releaseDelta;

                if (attackReleaseLevel <= 0.0f)
                {
                    stopNote (false);
                    break;
                }
            }

            if (outR != nullptr)
            {
                *outL++ += l;
                *outR++ += r;
            }
            else
            {
                *outL++ += (l + r) * 0.5f;
            }

            sourceSamplePosition += pitchRatio;

            if (sourceSamplePosition > playingSound->length)
            {
                stopNote (false);
                break;
            }
        }
    }
}
Exemplo n.º 9
0
void ArduboyTunes::stopScore (void)
{
  for (uint8_t i = 0; i < _tune_num_chans; i++)
    stopNote(i);
  tune_playing = false;
}
Exemplo n.º 10
0
OSStatus AKSampler_Plugin::HandleNoteOff(UInt8 inChannel, UInt8 inNoteNumber, UInt8 inVelocity, UInt32 inStartFrame)
{
    //printf("note off: ch%d nn%d vel%d\n", inChannel, inNoteNumber, inVelocity);
    stopNote(inNoteNumber, false);
    return noErr;
}
Exemplo n.º 11
0
int main(void) {
	// Setup pins
	DDRB = 0x00;	// All pins inputs
	DDRC = 0x07;	// PC0,1,2 as outputs for the LEDs
	DDRD = 0x0b;	// PD0 as output for LED, PD1 for TXD, PD3 for buzzer
	
	PORTD = 0xe0;	// Pullups for switches
	PORTB = 0x3f;	// Pullups for switches
	
	// LEDs
	LEDOff();
	
	// ADC
	ADMUX = 0x4f; // start off with mux on internal input
	ADCSRA = 0x80; // ADC EN
	DIDR0 = 0x38; // disable digital buffers on ADC pins
	
	// Timer
	TCCR0A = 0x00;  // Normal mode
	TCCR0B = 0x03;  // prescaler at 64 results in 125kHz ticks
	
	// UART
	cli(); // disable global interrupts

	stickInit(); // initialise stick input
	
	spektrumInit();
	
	// Get startup mode
	trainerPlugged = TRAINERPIN;
	bindSwitch = BINDPIN;
	transmitMode = 0;
	if(trainerPlugged == 0) transmitMode = eTM_trainer_master; // fixme or trainer slave, if PPM signal present

	if(bindSwitch == 0) transmitMode = eTM_bind;
	
	// Timer loop!
	static uint8_t fastScaler = 255;
	static uint8_t slowScaler = 255;
	static uint16_t secondScaler = 0xffff;
	
	// Buzzer
	TCCR2B = 0x03; // prescaler at 64
	stopNote();
	noteBuffer[0] = NOTE6G;
	noteBuffer[1] = NOTE6GS;

	noteBuffer[2] = NOTESTOP;
	noteCounter = 0;
	noteInterruptable = 0;
	

	
	while(1) {
		
		
		while(TCNT0 < FASTLOOPCOUNT)
		{
			if(TCNT0 < FASTLOOPCOUNT && TCNT0 >= FASTLOOPCOUNT-LED0Duty/3) LED0On(); // red LED too bright, reduce duty
			if(TCNT0 < FASTLOOPCOUNT && TCNT0 >= FASTLOOPCOUNT-LED1Duty) LED1On();
			if(TCNT0 < FASTLOOPCOUNT && TCNT0 >= FASTLOOPCOUNT-LED2Duty) LED2On();
			if(TCNT0 < FASTLOOPCOUNT && TCNT0 >= FASTLOOPCOUNT-LED3Duty) LED3On();
		}
		TCNT0 = 0; // reduce jitter by resetting soon (SPEKTRUM does not like jitter!)
		LEDOff();
		
		// should be going at about 625Hz
		getDigital();
		stickGetRawADC(rawstickvalues);
		
	
		if(++fastScaler >= OVERSAMPLE) //should be 22ms for DSMX (Nano Board can not handle it faster!!)
		{ 
			fastScaler = 0;
			
			// this loop runs slower than 50Hz
			fastLoop();
			/*throVoltage = 0;
			ruddVoltage = 0;
			elevVoltage = 0;
			aileVoltage = 0;*/
			rawstickvalues[0]=0;
			rawstickvalues[1]=0;
			rawstickvalues[2]=0;
			rawstickvalues[3]=0;
			
			if(++slowScaler >= 6) 
			{ // should be going at about 10Hz
				slowScaler = 0;
				slowLoop();
			}
		}
		
		// we are here every 2ms
		if(++secondScaler >=500)
		{
			// every second
			secondScaler=0;
			IdleSeconds++;
			if(auxSwitch && mixToggle)
			{	
				FlySeconds++;
					
				if(FlySeconds == FLYSECONDS_MAX)
					oneTone(NOTE7B);
				if(FlySeconds == FLYSECONDS_MAX+10)
					twoTone(NOTE7C);
			
				if(IdleSeconds >= FLYSECONDS_MAX*2)
				{
					twoTone(NOTE7C);
				}
			}
		}
	}
}
Exemplo n.º 12
0
void slowLoop(void) {
	// Read battery voltage
	ADMUX = 0x46;   // select chan 6
	ADCSRA |= 0x40; // start
	while(ADCSRA & 0x40); // wait for completion
	uint8_t level = ADC >> 2; // note: level is now approx 26.1x the actual voltage
	
	// Battery level pulse speeds
	if(level > 150) battPulse = 1; // slow pulse
	else if(level > 137) battPulse = 2;
	else if(level > 120) battPulse = 4;
	else {
		battPulse = 24; // very fast pulses
		
		if(noteBuffer[noteCounter] == 0) {
			noteBuffer[0] = NOTEPAUSE;
			noteBuffer[1] = NOTEPAUSE;
			noteBuffer[2] = NOTEPAUSE;
			noteBuffer[3] = NOTEPAUSE;
			noteBuffer[4] = NOTEPAUSE;
			noteBuffer[5] = NOTEPAUSE;
			noteBuffer[6] = NOTEPAUSE;
			noteBuffer[7] = NOTEPAUSE;
			noteBuffer[8] = NOTEPAUSE;
			noteBuffer[9] = NOTE5C;
			noteBuffer[10] = NOTE5C;
			noteBuffer[11] = NOTE5CS;
			noteBuffer[12] = NOTE5CS;
			noteBuffer[13] = NOTESTOP;
			noteCounter = 0;
			noteInterruptable = 1;
		}
	}
	
	// Check trainer plug plug
	static uint8_t trainerPluggedPrev;
	trainerPlugged = TRAINERPIN;
	if(trainerPlugged != trainerPluggedPrev) {
		trainerPluggedPrev = trainerPlugged;
		if(trainerPlugged) {
			// Not Plugged IN
			if(noteInterruptable) {
				noteBuffer[0] = NOTE6C;
				noteBuffer[1] = NOTE7C;
				noteBuffer[2] = NOTE6C;
				noteBuffer[3] = NOTESTOP;
				noteCounter = 0;
			}
			if(transmitMode == 2 || transmitMode == 3) transmitMode = 0; // escape out of trainer mode
		}
		else {
			// Plugged IN
			noteBuffer[0] = NOTE6C; // these notes interrupt the startup tune, this is by design
			noteBuffer[1] = NOTE6C;
			noteBuffer[2] = NOTE7C;
			noteBuffer[3] = NOTESTOP;
			noteCounter = 0;
			noteInterruptable = 0;
			if(transmitMode == 0) transmitMode = 3; // enter trainer mode
		}
	}
	
	// Beeps while toggling or binding beeps
	if(toggleCounter >= 4 || transmitMode == 1) {
		if(noteBuffer[noteCounter] == 0) {
			noteBuffer[0] = NOTEPAUSE;
			noteBuffer[1] = NOTEPAUSE;
			noteBuffer[2] = NOTEPAUSE;
			noteBuffer[3] = NOTEPAUSE;
			noteBuffer[4] = NOTEPAUSE;
			noteBuffer[5] = NOTEPAUSE;
			noteBuffer[6] = NOTEPAUSE;
			noteBuffer[7] = NOTEPAUSE;
			noteBuffer[8] = NOTEPAUSE;
			noteBuffer[9] = NOTE5C;
			noteBuffer[10] = NOTESTOP;
			noteCounter = 0;
			noteInterruptable = 1;
		}
	}
	
	// Play sounds
	if(noteBuffer[noteCounter] > 0) {
		if(noteBuffer[noteCounter] == NOTEPAUSE) {
			stopNote();
		}
		else {
			playNote(noteBuffer[noteCounter]);
		}
		noteCounter++;
	}
	else {
		stopNote();
		noteInterruptable = 1;
	}
}