Search
  • Tim Cave

Making a VSTi Host - Part 6

Updated: Jan 21, 2021

In this blog I want to get midi hooked up and do the following:

  1. Play a single note

  2. Hook up a midi keyboard

  3. Stream a midi file through a VSTi

  4. Load up a grid of notes

  5. Play midi file through ASIO

Go back to the Un4seen site download the bassmidi package. I also recommend downloading the latest bass.dll and placing it in your Debug folder as the midi setting for the keyboard input, to avoid any lag, is in the latest version.


Play a Single Note

Playing a single note can be done with the following line of code inserted into our list of functions created in part 5 of this series as follows:



        InitBASS();
	FreeVSTChannelStream();
	CreateVSTChannelStream();
	
	// press note #60 (middle C) on channel #1
	BASS_VST_ProcessEvent(vststream, 0, MIDI_EVENT_NOTE, MAKEWORD(60, 100));
		
	MessageBox::Show("Playing");//stops Channel being freed up

	BASS_VST_ChannelFree(vststream);
	CleanupAndShutdown();

A chord can be created by simple adding more events.


Hook up a midi keyboard

To add a midi input you need to know the device number you want to use. Add a button to your form and then add this code in the header file:


	const char* charstr = "";
	int a;
	BASS_MIDI_DEVICEINFO midiinfo;
	for (a = 0; BASS_MIDI_InGetDeviceInfo(a, &midiinfo); a++) {
		charstr = midiinfo.name;
		String^ clistr = gcnew String(charstr);
		listBox1->Items->Add("Device number = " + a.ToString() + " with name " + clistr);
	}

My set up indicates that I need device number 1 for my Oxygen 49 keyboard.



Armed with this information you can now set up a function and call the following code:



void loadMidiKeyboard() {

	BASS_Init(-1, 44100, 0, 0, 0); //Initializes BASS Library.
	BASS_SetConfigPtr(BASS_CONFIG_MIDI_DEFFONT, "c:\\temp\\sf_GMbank.sf2");
	
	stream = BASS_MIDI_StreamCreate(16, 0, 0); // create a MIDI stream to play the MIDI data
	
	//sorts out lag issue - if you get an error than downlaod latest bass.dll 
	if(!BASS_ChannelSetAttribute(stream, BASS_ATTRIB_BUFFER, 0))
	{
		MessageBox::Show("BASS_ChannelSetAttribute " + BASS_ErrorGetCode());
	}	

	//midi file stream
   //stream = BASS_MIDI_StreamCreateFile(false, "c:\\temp\\row1.mid", 0, 0, BASS_STREAM_AUTOFREE, 44100);
	HSOUNDFONT sfont1 = BASS_MIDI_FontInit("c:\\temp\\sf_GMbank.sf2", 0);
	BASS_MIDI_FONT fonts[2];
	fonts[0].font = sfont1;
	fonts[0].preset = 0; // preset 0
	fonts[0].bank = 0; // bank 0
	BASS_MIDI_StreamSetFonts(stream, fonts, 2); // apply it to the stream  
	 // create a MIDI input callback delegate
	MIDIINPROC MidiInProc;
	BASS_MIDI_InInit(1, MidiInProc, 0);
	BASS_MIDI_InStart(1);

	BASS_ChannelPlay(stream, true);
	MessageBox::Show("Play Sound Font");
	//Plays the Stream.
	BASS_Free();
}

Note for this demo I'm using a soundfont for the sound.


Stream a midi file through a VSTi

This is the point when I gave up on the C# version shown in Part 1 and moved to C++ as you have to use a Callback function for the midi events.


Here I've set up a new function call loadMidi as follows:


void loadMidi() {

	midistream = BASS_MIDI_StreamCreateFile(false, "C:\\temp\\row1.mid", 0, 0, BASS_SAMPLE_FLOAT | BASS_STREAM_DECODE, 0); // create MIDI stream from file
	
	eventc = BASS_MIDI_StreamGetEvents(midistream, -1, 0, NULL); // get number of events in it
    events = new BASS_MIDI_EVENT[eventc]; // allocate event array
    int count = BASS_MIDI_StreamGetEvents(midistream, -1, 0, events); // get the events
	outpos, eventpos; // playback positions
	outstream = BASS_StreamCreate(44100, 2, BASS_SAMPLE_FLOAT, VstStreamProc, NULL); // create output		
}

And then you need the callback function for the VstStreamProc



DWORD CALLBACK VstStreamProc(HSTREAM handle, void* buffer, DWORD length, void* user)
{
	DWORD done = 0;
	while (done < length) {
		// apply events at this position
		while (eventpos < eventc && events[eventpos].pos <= outpos) {
			BASS_VST_ProcessEvent(vststream, events[eventpos].chan, events[eventpos].event, events[eventpos].param);
			eventpos++;
		}
		// get the data (up to next event)
		DWORD block = length - done;
		if (eventpos < eventc && block > events[eventpos].pos - outpos) block = events[eventpos].pos - outpos;
		int got = BASS_ChannelGetData(vststream, (BYTE*)buffer + done, block);
		if (got <= 0) break; // VST problem?
		outpos += got;
		done += got;
	}
	if (done < length || eventpos == eventc) done |= BASS_STREAMPROC_END; // end of stream
	return done;
}

Finally use the outstream as follows:


	outpos = eventpos = 0; // reset playback positions
	BASS_ChannelPlay(outstream, false); // start playback
	MessageBox::Show("Playing");//stops Channel being freed up
	BASS_VST_ChannelFree(outstream);

If you're wondering about the MessgeBox looking a bit clunkly you'd be right! Ian @ Un4Seen says I need to use Sync - it's on the list!


If all goes well with the code then your midi file will play!


Load up a grid of notes

My original project idea was to have a grid of notes or coloured squares to trigger the Arcade VSTi. I've done this by adding a dataGridView and reading the data into an array as follows:



int midiInfo[2][15] = { {48, 50, 52, 53, 55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72},{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } };
	
	//read first row in array	
	for (int readKeys = 0; readKeys < 15; readKeys++) {

			for (int b = 1; b < 9; b++) {
					if ((dataGridView1->Rows[0]->Cells[readKeys]->Value->ToString()) == b.ToString()) {
					midiInfo[1][readKeys] = b;
					}
				}
			
		}		
		//pass array  
		getGridDataRow1(midiInfo);



This code is in the header file and getGridDataRow1 function is in the cpp file as follows:



//note 48, 50, 52, 53, 55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72
	//convert array to midi file
	smf::MidiFile midifile;
	int track = 0;
	int channel = 0;
	int instr = 0; //piano
	midifile.addTimbre(track, 0, channel, instr);
	int t = midifile.getTicksPerQuarterNote();
	int tpq = midifile.getTPQ();
	int starttick = 0;
	for (int f = 0; f < 15; f++) {
		int endtick = starttick + (midiInfo[1][f] * tpq);
		if (midiInfo[1][f])
		{
			midifile.addNoteOn(track, starttick, channel, midiInfo[0][f], 100);
			midifile.addNoteOff(track, endtick, channel, midiInfo[0][f]);
		}
	}
	midifile.sortTracks();
    // Need to sort tracks since added events are							
	midifile.write("C:\\temp\\row1.mid");	

This will turn the grid data into a midi file.

In order to make this work I downloaded the very comprehensive Midifile library from here as the bassmidi documentation is rather sparse..


Now you can play the midi file as before.


Play midi file through ASIO

The final part of this blog is to play the midi file through ASIO.

I did get a bit stuck on this one and once again Ian @ Un4Seen helped out!


First of all download the BassAsio package and add the usual header, lib and dll files to your project.


As with midi devices you need to know the number of the device to use - I'm using device number 1.


Now add the following code to a function:



void loadVSTiASIO() {
	
	BASS_CHANNELINFO i;
	BASS_Init(0, 44100, 0, 0, NULL);

	//assign a VSTi plugin to the channel
	vststream = BASS_VST_ChannelCreate(44100, 2, "C:\\Program Files (x86)\\Vstplugins\\DSK The Grand.dll", BASS_SAMPLE_LOOP | BASS_STREAM_DECODE | BASS_SAMPLE_FLOAT);

	midistream = BASS_MIDI_StreamCreateFile(false, "C:\\temp\\row1.mid", 0, 0, BASS_SAMPLE_FLOAT | BASS_STREAM_DECODE, 0); // create MIDI stream from file
	eventc = BASS_MIDI_StreamGetEvents(midistream, -1, 0, NULL); // get number of events in it
	events = new BASS_MIDI_EVENT[eventc]; // allocate event array
	BASS_MIDI_StreamGetEvents(midistream, -1, 0, events); // get the events

	BASS_VST_INFO vstInfo; //vst info 	
	//pick the ASIO driver you want to use
	if (!BASS_ASIO_Init(1, BASS_ASIO_THREAD)) // initialize ASIO device
		MessageBox::Show("Can't initialize ASIO device");

	outstream = BASS_StreamCreate(44100, 2, BASS_SAMPLE_FLOAT | BASS_STREAM_DECODE, VstStreamProc, NULL);
	if (!BASS_ASIO_ChannelEnableBASS(FALSE, 0, outstream, TRUE))	
		MessageBox::Show("Can't enable ASIO channel(s)");
	BASS_ChannelGetInfo(vststream, &i);
	if (i.chans == 1) BASS_ASIO_ChannelEnableMirror(1, FALSE, 0); // mirror mono channel to form stereo output
	BASS_ASIO_SetRate(i.freq); // try to set the device rate to avoid resampling
	BASS_ASIO_Start(0, 0);   // start the device using default buffer/latency

	outstream = BASS_StreamCreate(44100, 2, BASS_SAMPLE_FLOAT, VstStreamProc, NULL); // create output	
	outpos, eventpos; // playback positions	

	MessageBox::Show("Playing ASIO");

	BASS_ASIO_Free();
	BASS_Free();

	
} 

If all goes well you should hear the midi file being played through the VSTi - in my app demo I've constructed a C major chord that is 4 beats long and sounds like this:















9 views0 comments

Recent Posts

See All