How to deal with MIDI protocol |
|
MIDI (Musical Instrument Digital Interface) is an industry-standard protocol, first defined in 1982, that enables electronic musical instruments (like synthesizers and drum machines), computers and other electronic equipment (like MIDI controllers, sound cards and samplers) to communicate and synchronize with each other. MIDI's primary functions include communicating event messages about musical notation, pitch, velocity, control signals for parameters (such as volume, vibrato, panning, cues, and clock signals) between two devices in order to complete a signal chain and produce audible sound from a sound source. As an electronic protocol, it is notable for its widespread adoption throughout the music industry.
MIDI synthesizers use wavetables to define the base samples that are used to render MIDI files. MIDI files in themselves don't contain any sounds, rather they contain only instructions to render them, and consequently rely on wavetables to render such sounds correctly. For this purposes there is the need to rely on SoundFonts files (usually identified through their ".SF2" extension) which contain base samples in PCM format (similar to WAV files) that are mapped to sections on a musical keyboard. A SoundFont bank also contains other music synthesis parameters such as loops, vibrato effect, and velocity sensitive volume changing.
When dealing with MIDI streams, in order to have a better response time and granularity for MIDI events, it's recommended to keep latency as short as possible (see the How to deal with latency tutorial for details) and to keep a small time for updating the DirectSound buffer: usually by setting a value of 10 milliseconds for the BufferUpdateTime property gives good results.
Active DJ Studio, through the MIDI property which encapsulates functionalities of the MIDI COM object, can manage different aspects of MIDI protocol:
Loading and playback of MIDI files
MIDI files can be loaded inside a player instanced by the control through one of the LoadSound based methods and, as seen for any other audio format, can be played through the PlaySound method; once a sound file is loaded, you can check if the loaded sound is a MIDI stream by checking if the value returned by the GetFileType method is SOUNDTYPE_MIDI.
It's important to note that, without SoundFonts installed inside the system, there is no possibility to hear sound coming out from speakers of your PC, also if a MIDI file has been loaded correctly; see the Management of MIDI SoundFonts section below for details about SoundFonts management.
Duration of MIDI files and position detection/modification during playback can be retrieved using regular methods like SoundDurationGet, SoundPositionGet and SeekSound but, due to the fact that MIDI files positioning can be measured in "MIDI ticks" as well, there is the possibility to use MIDI specific alternatives like MIDI.SoundDurationGet, MIDI.SoundPositionGet and MIDI.SeekToPosition.
Another feature common with other audio formats, but with MIDI specific capability to be expressed in MIDI ticks, are triggers so you could use the TriggersAdd and TriggersSetPos methods but also the MIDI.TriggerAdd and MIDI.TriggerSetPos methods in case you should setting the trigger position at a specific MIDI tick.
MIDI files are much smaller than recorded audio waveforms because the file stores instructions (also known as events) on how to recreate the sound based on synthesis with a MIDI synthesizer rather than an exact waveform to be reproduced: typical events used for playback purposes are music notes.
During playback of MIDI files, you can be notified of each specific MIDI event occurring by enabling their real-time detection through the MIDI.StreamEventsEnableNotifications method: after enabling detection, the container application can be notified about occurring MIDI events through the MidiStreamEventNotification event.
Without the need to start playback, you can enumerate all of the events stored inside the MIDI file through the MIDI.StreamEventsEnum method (or through the MIDI.StreamEventsEnumFromRange method if you only need to parse a smaller portion of the MIDI file) and you can get information about each of the detected events through the MIDI.StreamEventsEnumItemGet method.
The current value of a MIDI event can be obtained through the MIDI.StreamEventValueGet method: the only exception is related to music notes because more than one could be applied at the same time.
MIDI files can also contain other information not directly related to playback of music notes:
• | Meta tags: Each MIDI track (you can get the number of tracks through the MIDI.TrackCountGet method) can contain meta tags in textual form: these tags can be enumerated through the MIDI.TrackTagsEnum method and related information of each meta tag can be retrieved through the MIDI.TrackTagsItemGet method. |
• | Markers: may contain lyric text, copyright text, time signatures and so on; markers detection can be performed in real-time during playback by enabling their notification through the MIDI.MarkersEnableNotifications method so, when a marker is encountered, the MidiMarkerNotification event is fired. Markers detection can be also performed before playback by enumerating them through the MIDI.MarkersEnum method and by getting information for each of the through the MIDI.MarkersEnumItemGet method. |
Tempo management during playback of MIDI files is available through the MIDI.TempoPercGet and MIDI.TempoPercSet methods and also BPM (Beats per MInute) management is available through the MIDI.BpmGet and MIDI.BpmSet methods.
Volume playback for each MIDI track can be retrieved or modified through the MIDI.TrackVolumeGet and MIDI.TrackVolumeSet methods.
Creation of an empty playback MIDI stream on which MIDI events can be applied
In some case it may be needed to have the possibility to play MIDI notes without the need to previously load a MIDI file; for this purpose you can create and immediately put in playback mode an empty MIDI stream through the MIDI.StreamStart method. This MIDI stream can be stopped and destroyed from memory through the MIDI.StreamStop method.
Once the MIDI stream is started, you have the possibility to apply single MIDI events through the MIDI.StreamEventApply method or, in one single shot, a list of MIDI events through the MIDI.StreamEventsListApply method. The list of MIDI events to apply in one single shot can be edited using a set of dedicated methods:
• | MIDI.StreamEventsListItemAdd adds a new MIDI event to the list and returns back a unique identifier for the MIDI event: this unique identifier allows discriminating MIDI events from each other |
• | MIDI.StreamEventsListItemModify allows modifying a specific MIDI event already added to the list |
• | MIDI.StreamEventsListItemRemove allows removing an event from the list or to clear all contents from the list |
• | MIDI.StreamEventsListItemGet allows retrieving information about a specific MIDI event already added to the list |
• | MIDI.StreamEventsListItemCountGet allows retrieving the number of MIDI events added to the list |
• | MIDI.StreamEventsListItemUniqueIdGet given a zero-based index allows retrieving the unique identifier of the corresponding MIDI event |
The list stays available in memory also after having called the Midi.StreamEventsListApply method so it can be applied more than once.
MIDI events are sometime available also in raw format inside a memory buffer: in this case you can apply raw MIDI data through the MIDI.StreamEventsRawApply method.
All of the MIDI events related methods described above can be used during playback of MIDI files as well.
Acquisition of MIDI events from a MIDI input device
MIDI input devices installed inside the system can be enumerated through the MIDI.InputDevicesEnum method. After completing the enumeration, the number of installed MIDI input devices can be retrieved through the MIDI.InputDevicesCountGet method and information about each device can be obtained through the MIDI.InputDevicesInfoGet method.
When you need to get MIDI events from one of the listed MIDI devices, you need to start the device using one of the methods below:
• | MIDI.InputDevicesStart starts the MIDI input device and allows receiving raw MIDI events inside a memory buffer received by a predisposed callback function |
• | MIDI.InputDevicesStartOnPlayer starts the MIDI input device and sends incoming raw MIDI events directly to the given player, allowing a direct playback of notes coming from the device |
In both cases input from the MIDI device can be stopped through the MIDI.InputDevicesStop method.
As mentioned above, the availability of SoundFonts (identified through their extension ".SF2") is mandatory in order to perform playback of MIDI files or of MIDI events. By default the component will try to see if Creative SoundFonts (CT2MGM.SF2, CT4MGM.SF2, CT8MGM.SF2 and 28MBGM.SF2, ) are available inside the Windows\System32 folder: if one of them should be available, it will be automatically loaded so MIDI playback will be immediately possible.
In order to see if a default SoundFont is available you could call the MIDI.SoundFontDefaultGet method and check if the returned string contains the pathname of a SF2 file. This default can be changed at any time through the MIDI.SoundFontDefaultSet method.
The default SoundFont affects all MIDI streams loaded from a MIDI file or created from scratch through the MIDI.StreamStart method. If you should need to set a specific SoundFont for a certain player instanced by the component, you should initialize a SF2 file through one between the MIDI.SoundFontInit or MIDI.SoundFontInitFromMemory or MIDI.SoundFontInitFromMemory methods and, through the returned unique identifier, assign the SoundFont to the player through the MIDI.SoundFontApply method.
When a SoundFont has been initialized, you can retrieve information about it, such as its friendly name, through the MIDI.SoundFontInfoStringGet and MIDI.SoundFontInfoNumGet methods and, when the SoundFont is no more needed, you can discard it from memory through the MIDI.SoundFontFree method.
Each SoundFont can contain up to 128 presets: you can obtain information about each single preset, for example its friendly description, through the MIDI.SoundFontPresetDescGet method.
In case you should need to apply to a certain player multiple SoundFonts in one single shot, you could create a list of SoundFonts to apply through the MIDI.SoundFontListApply method. This list can be edited through the following methods:
• | MIDI.SoundFontListItemAdd adds a new SoundFont to the list: the SoundFont must have been previously initialized through the MIDI.SoundFontInit method. |
• | MIDI.SoundFontListItemRemove allows removing a SoundFont from the list |
• | MIDI.SoundFontListItemCountGet allows retrieving the number of SoundFont added to the list |
• | MIDI.SoundFontListItemUniqueIdGet given a zero-based index allows retrieving the unique identifier of the corresponding SoundFont |
Finally you can enumerate SoundFonts applied to a MIDI stream through the MIDI.SoundFontsInStreamEnum method and to obtain information about each of them through the MIDI.SoundFontsInStreamItemGet method.
In order to avoid CPU spikes during playback of MIDI streams you can load presets of the sound font using the MIDI.SoundFontLoad method or pre-load samples of the MIDI file using the MIDI.StreamPreloadSamples method.
Usage of the virtual piano keyboard
Active DJ Studio implements also a graphical gadget called "virtual piano keyboard" which allows playing notes by pressing the mouse over the available keys or to display notes being played by an external MIDI physical keyboard connected to the system.
The virtual piano keyboard can be displayed with three different orientations as seen on the screenshots below
Horizontal
Vertical left
Vertical right
and can also have a variable number of keys with a maximum of 128; below you will find the keyboard as seen when launching the sample MidiSynthesizer and pressing a key.
The virtual piano keyboard can be instanced on the user interface of your application through the MIDI.KeyboardCreate method; as for all of the other windows control, the virtual piano keyboard can be moved and resized (MIDI.KeyboardMove method), shown or hidden (MIDI.KeyboardShow method), refreshed (MIDI.KeyboardRefresh method) or destroyed (MIDI.KeyboardDestroy method).
Once instanced, the user can start playing notes by clicking keys through the left button of the mouse (but also through his fingers in case of availability of a touch screen) or by calling the MIDI.KeyboardNote method. The container application can be informed when a specific key is pressed or released through the MidiKeyboardNotification event.
In case you should simply need to display on the virtual piano keyboard that a note was played through an external MIDI input device connected to the system, the MIDI.KeyboardNote method allows simulating a key pressure without generating a clone of the same note: the purpose of its bSimulated parameter is exactly this. This feature is quite easy to implement through the following procedure:
• | After having enumerated MIDI input devices through the MIDI.InputDevicesEnum method, start getting MIDI data from the external MIDI input device through the MIDI.InputDevicesStartOnPlayer method |
• | Enable notifications of MIDI notes events (MIDI_EVENT_NOTE) through the MIDI.StreamEventsEnableNotifications method |
• | When the MidiStreamEventNotification event is generated, check if its nMidiEvent parameter is set to MIDI_EVENT_NOTE: if it should, call the MIDI.KeyboardNote method passing the note to play (and related velocity) and setting the bSimulated parameter to BOOL_TRUE |
The range of available keys can be modified at any time through the MIDI.KeyboardRangeSet method and its graphical settings can be retrieved and modified through the MIDI.KeyboardGraphicalSettingsGet and MIDI.KeyboardGraphicalSettingsSet methods.
Samples of management of MIDI protocol in Visual C++ 6 and Visual Basic 6 can be found inside the following samples installed with the product's setup package:
- MidiPlayer
- MidiSynthesizer