Stuck trying to move an object when function is called

I am making a MIDI visualizer, and I need for when a note plays (using CSharpSynth) a function is called. And it is, so that part is down. But what’s frustrating is trying to get that function to do something; currently, I am able to Debug.Log the current note that is being played, but when I put something in like set an object’s X position to the note number…

public void MidiNoteOnHandler(int channel, int note, int velocity)
    {
		transform.position = new Vector3(note, 0, 0);

    }

I get an error:
get_transform can only be called from the main thread.

What’s the best way to get around this? Make a new script and then somehow whenever MidiNoteOnHandler is called it will fire it to another script? Sorry if my vocabulary is bad. Here’s the full script if needed.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using CSharpSynth.Effects;
using CSharpSynth.Sequencer;
using CSharpSynth.Synthesis;
using CSharpSynth.Midi;

[RequireComponent(typeof(AudioSource))]
public class MIDIPlayer : MonoBehaviour
{
    //Public
    //Check the Midi's file folder for different songs
    public string midiFilePath = "Midis/Groove.mid";
    
    //Try also: "FM Bank/fm" or "Analog Bank/analog" for some different sounds
    public string bankFilePath = "GM Bank/gm";
    public int bufferSize = 1024;
    public int midiNote = 60;
    public int midiNoteVolume = 100;
    public int midiInstrument = 1;
    //Private 
    private float[] sampleBuffer;
    private float gain = 1f;
    public MidiSequencer midiSequencer;
    private StreamSynthesizer midiStreamSynthesizer;

    private float sliderValue = 1.0f;
    private float maxSliderValue = 127.0f;

	//For animation

    // Awake is called when the script instance
    // is being loaded.
    void Awake()
    {
        midiStreamSynthesizer = new StreamSynthesizer(44100, 2, bufferSize, 40);
        sampleBuffer = new float[midiStreamSynthesizer.BufferSize];

        midiStreamSynthesizer.LoadBank(bankFilePath);

        LoadSong(midiFilePath);

        //These will be fired by the midiSequencer when a note plays. Check the console for messages if you uncomment these
        midiSequencer.NoteOnEvent += new MidiSequencer.NoteOnEventHandler (MidiNoteOnHandler);
        midiSequencer.NoteOffEvent += new MidiSequencer.NoteOffEventHandler (MidiNoteOffHandler);			
    }

    void LoadSong(string midiPath)
    {
        midiSequencer = new MidiSequencer(midiStreamSynthesizer);
        midiSequencer.LoadMidi(midiPath, false);
        midiSequencer.Play();
    }

    // Start is called just before any of the
    // Update methods is called the first time.
    void Start()
    {
    }

    // Update is called every frame, if the
    // MonoBehaviour is enabled.
    void Update()
    {
        if (!midiSequencer.isPlaying)
            //if (!GetComponent<AudioSource>().isPlaying)
            LoadSong(midiFilePath);
    }

    // See http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.OnAudioFilterRead.html for reference code
    //	If OnAudioFilterRead is implemented, Unity will insert a custom filter into the audio DSP chain.
    //
    //	The filter is inserted in the same order as the MonoBehaviour script is shown in the inspector. 	
    //	OnAudioFilterRead is called everytime a chunk of audio is routed thru the filter (this happens frequently, every ~20ms depending on the samplerate and platform). 
    //	The audio data is an array of floats ranging from [-1.0f;1.0f] and contains audio from the previous filter in the chain or the AudioClip on the AudioSource. 
    //	If this is the first filter in the chain and a clip isn't attached to the audio source this filter will be 'played'. 
    //	That way you can use the filter as the audio clip, procedurally generating audio.
    //
    //	If OnAudioFilterRead is implemented a VU meter will show up in the inspector showing the outgoing samples level. 
    //	The process time of the filter is also measured and the spent milliseconds will show up next to the VU Meter 
    //	(it turns red if the filter is taking up too much time, so the mixer will starv audio data). 
    //	Also note, that OnAudioFilterRead is called on a different thread from the main thread (namely the audio thread) 
    //	so calling into many Unity functions from this function is not allowed ( a warning will show up ). 	
    private void OnAudioFilterRead(float[] data, int channels)
    {
        //This uses the Unity specific float method we added to get the buffer
        midiStreamSynthesizer.GetNext(sampleBuffer);

        for (int i = 0; i < data.Length; i++)
        {
            data <em>= sampleBuffer _* gain;_</em>

}
}

public void MidiNoteOnHandler(int channel, int note, int velocity)
{
* transform.position = new Vector3(note, 0, 0);*

}

public void MidiNoteOffHandler(int channel, int note)
{

}
}

Audio is usually handled on a separate thread from the (main) render thread. This means, you’re always getting good sound without stuttering, even if the framerate drops, but on the other hand it means, you can’t directly call Unity API code from within callbacks from the audio side.

Multithreading is a difficult topic, but for starters you could try this approach:

  1. Receive the callback and instead of setting the position, only set a Vector3 member variable.
  2. In Update poll for changes or just update the transform.position to the stored member variable.

This means that your are going to set the variable at different times, maybe even multiple times and not in sync with update, but it should generally work, if there is only one handler, which sets the variable.

Next, if you’re seeing jerky movement or anything you might need to smooth out the position update by lerping for example.

private Vector3 m_NextPos;

public void MidiNoteOnHandler(int channel, int note, int velocity)
{
    m_NextPos = new Vector3(note, 0, 0);
}

private void Update()
{
    transform.position = m_NextPos;
}