Related
So I have been working on updates to a fairly simple and task-specific image processing app that I rolled out some time ago. Due to a tendency of the imaging techs who use this software to mess with their scanner settings in ways that require the program to accommodate unnecessary changes, sometimes intentionally and sometimes accidentally, I wanted to add a Scan button in the update that will standardize things like image resolution and color settings in order to enforce uniformity while also reducing the number of programs employees have to have open and switch between. Initially I tried to accomplish this with a Powershell script that was called by the original python program. That was a nightmare, is not what I am doing now, and this is not a duplicate of the question I posted with regards to that problem. So, on to the problem:
Rather than sticking with python and Powershell, I wrote the upgraded app in C#, using WIA to handle the scanner and Aforge.Net to perform image post-processing tasks. I have code that works reasonably well, finds the scanner, and scans the image in color with the appropriate size, resolution, and compression. The issue is that this is still not really a "photo-quality" image. We are scanning comic books and things like smudges and creases in the cover have to be visible on all scans, even very dark ones. The Epson scan manager accomplishes this pretty well, though it washes out the images a bit in the process, but I can't figure out what settings I should change in order to achieve a similar end. As an example, here is a test image scanned with the scan button on my app:
And here is the same image scanned using the Epson Scan Manager:
I basically want to know how I get the top image to look more like the bottom image. It doesn't have to be exactly the same, but I need to be able to see all those smudges and imperfections, or at least as many of them as possible. I can pretty easily imitate the general look of the bottom image with image filters, but I can't use that to get information that the scanner didn't get. Post-processing won't necessarily get me those smudges back. I need to adjust how the image is taken. In theory, I know I should be able to play with something like exposure times and the like but I can't even find appropriate constants for that sort of thing as the documentation is somewhat opaque. Here is the code I currently have for accessing the scanner and getting the scan:
private static void AdjustScannerSettings(IItem scannerItem, int scanResolutionDPI, int scanStartLeftPixel, int scanStartTopPixel, int scanWidthPixels, int scanHeightPixels, int brightnessPercents, int contrastPercents, int colorMode)
{
const string WIA_SCAN_COLOR_MODE = "6146";
const string WIA_HORIZONTAL_SCAN_RESOLUTION_DPI = "6147";
const string WIA_VERTICAL_SCAN_RESOLUTION_DPI = "6148";
const string WIA_HORIZONTAL_SCAN_START_PIXEL = "6149";
const string WIA_VERTICAL_SCAN_START_PIXEL = "6150";
const string WIA_HORIZONTAL_SCAN_SIZE_PIXELS = "6151";
const string WIA_VERTICAL_SCAN_SIZE_PIXELS = "6152";
const string WIA_SCAN_BRIGHTNESS_PERCENTS = "6154";
const string WIA_SCAN_CONTRAST_PERCENTS = "6155";
SetWIAProperty(scannerItem.Properties, "4104", 24);
SetWIAProperty(scannerItem.Properties, WIA_HORIZONTAL_SCAN_RESOLUTION_DPI, scanResolutionDPI);
SetWIAProperty(scannerItem.Properties, WIA_VERTICAL_SCAN_RESOLUTION_DPI, scanResolutionDPI);
SetWIAProperty(scannerItem.Properties, WIA_HORIZONTAL_SCAN_START_PIXEL, scanStartLeftPixel);
SetWIAProperty(scannerItem.Properties, WIA_VERTICAL_SCAN_START_PIXEL, scanStartTopPixel);
SetWIAProperty(scannerItem.Properties, WIA_HORIZONTAL_SCAN_SIZE_PIXELS, scanWidthPixels);
SetWIAProperty(scannerItem.Properties, WIA_VERTICAL_SCAN_SIZE_PIXELS, scanHeightPixels);
SetWIAProperty(scannerItem.Properties, WIA_SCAN_BRIGHTNESS_PERCENTS, brightnessPercents);
SetWIAProperty(scannerItem.Properties, WIA_SCAN_CONTRAST_PERCENTS, contrastPercents);
SetWIAProperty(scannerItem.Properties, WIA_SCAN_COLOR_MODE, colorMode);
}
private static void SetWIAProperty(IProperties properties, object propName, object propValue)
{
Property prop = properties.get_Item(ref propName);
prop.set_Value(ref propValue);
}
private void buttonScan_Click(object sender, EventArgs e)
{
var deviceManager = new DeviceManager();
DeviceInfo firstScannerAvailable = null;
for (int i = 1; i <= deviceManager.DeviceInfos.Count; i++)
{
if (deviceManager.DeviceInfos[i].Type != WiaDeviceType.ScannerDeviceType)
{
continue;
}
firstScannerAvailable = deviceManager.DeviceInfos[i];
break;
}
var device = firstScannerAvailable.Connect();
var scannerItem = device.Items[1];
int resolution = 300;
int width_pixel = 3510;
int height_pixel = 5100;
int color_mode = 1;
AdjustScannerSettings(scannerItem, resolution, 0, 0, width_pixel, height_pixel, 0, 0, color_mode);
var imageFile = (ImageFile)scannerItem.Transfer("{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}");
var pathbase = Path.Combine(pictures, basedaemonpath);
string filebase = DateTime.Now.ToString("dd-MM-yyyy-hh-mm-ss-fffffff") + ".jpg";
var path = Path.Combine(pathbase, filebase);
WIA.ImageProcess myip = new WIA.ImageProcess(); // use to compress jpeg.
myip.Filters.Add(myip.FilterInfos["Convert"].FilterID);
myip.Filters[1].Properties["FormatID"].set_Value("{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}");
myip.Filters[1].Properties["Quality"].set_Value(84);
ImageFile image = myip.Apply(imageFile);
image.SaveFile(path);
}
I can include the post-processing code as well if it is needed but there is a lot of it (It is the primary function of the app after all) and all it really does is get a bunch of information about the content of the image and then rotate and crop it. It shouldn't have an effect on actual look of the image with the exception of the rotation and crop, so I'm leaving this part out for now. If snippets of this code are necessary let me know and I will post them. Thanks for any help you might be able to provide!
You need to decrease the contrast and increase the brightness to get the expected results.
According to this Microsoft WIA page. The valid ranges are from -1000 to 1000.
Make the following adjustments to the buttonScan_Click method:
// ...
int width_pixel = 3510;
int height_pixel = 5100;
int color_mode = 1;
// Add the following two lines
int brightness = 500;
int contrast = -500;
// Change the 0, 0 to brightness, contrast in the next line.
AdjustScannerSettings(scannerItem, resolution, 0, 0, width_pixel, height_pixel, brightness, contrast, color_mode);
You will have to adjust the values according to the results.
I have been playing around with SharpDX.XAudio2 for a few days now, and while things have been largely positive (the odd software quirk here and there) the following problem has me completely stuck:
I am working in C# .NET using VS2015.
I am trying to play multiple sounds simultaneously.
To do this, I have made:
- Test.cs: Contains main method
- cSoundEngine.cs: Holds XAudio2, MasteringVoice, and sound management methods.
- VoiceChannel.cs: Holds a SourceVoice, and in future any sfx/ related data.
cSoundEngine:
List<VoiceChannel> sourceVoices;
XAudio2 engine;
MasteringVoice master;
public cSoundEngine()
{
engine = new XAudio2();
master = new MasteringVoice(engine);
sourceVoices = new List<VoiceChannel>();
}
public VoiceChannel AddAndPlaySFX(string filepath, double vol, float pan)
{
/**
* Set up and start SourceVoice
*/
NativeFileStream fileStream = new NativeFileStream(filepath, NativeFileMode.Open, NativeFileAccess.Read);
SoundStream soundStream = new SoundStream(fileStream);
SourceVoice source = new SourceVoice(engine, soundStream.Format);
AudioBuffer audioBuffer = new AudioBuffer()
{
Stream = soundStream.ToDataStream(),
AudioBytes = (int)soundStream.Length,
Flags = SharpDX.XAudio2.BufferFlags.EndOfStream
};
//Make voice wrapper
VoiceChannel voice = new VoiceChannel(source);
sourceVoices.Add(voice);
//Volume
source.SetVolume((float)vol);
//Play sound
source.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo);
source.Start();
return voice;
}
Test.cs:
cSoundEngine engine = new cSoundEngine();
total = 6;
for (int i = 0; i < total; i++)
{
string filepath = System.IO.Directory.GetParent(System.IO.Directory.GetCurrentDirectory()).Parent.FullName + #"\Assets\Planet.wav";
VoiceChannel sfx = engine.AddAndPlaySFX(filepath, 0.1, 0);
}
Console.Read(); //Input anything to end play.
There is currently nothing worth showing in VoiceChannel.cs - it holds 'SourceVoice source' which is the one parameter sent in the constructor!
Everything is fine and well running with up to 5 sounds (total = 5). All you hear is the blissful drone of Planet.wav. Any higher than 5 however causes the console to freeze for ~5 seconds, then close (likely a c++ error which debugger can't handle). Sadly no error message for us to look at or anything.
From testing:
- Will not crash as long as you do not have more than 5 running sourcevoices.
- Changing sample rate does not seem to help.
- Setting inputChannels for master object to a different number makes no difference.
- MasteringVoice seems to say the max number of inputvoices is 64.
- Making each sfx play from a different wav file makes no difference.
- Setting the volume for sourcevoices and/or master makes no difference.
From the XAudio2 API Documentation I found this quote: 'XAudio2 removes the 6-channel limit on multichannel sounds, and supports multichannel audio on any multichannel-capable audio card. The card does not need to be hardware-accelerated.'. This is the closest I have come to finding something that mentions this problem.
I am not well experienced with programming sfx and a lot of this is very new to me, so feel free to call me an idiot where appropriate but please try and explain things in layman terms.
Please, if you have any ideas or answers they would be greatly appreciated!
-Josh
As Chuck has suggested, I have created a databank which holds the .wav data, and I just reference the single data store with each buffer. This has improved the sound limit up to 20 - however this has not fixed the problem as a whole, likely because I have not implemented this properly.
Implementation:
class SoundDataBank
{
/**
* Holds a single byte array for each sound
*/
Dictionary<eSFX, Byte[]> bank;
string curdir => Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName;
public SoundDataBank()
{
bank = new Dictionary<eSFX, byte[]>();
bank.Add(eSFX.planet, NativeFile.ReadAllBytes(curdir + #"\Assets\Planet.wav"));
bank.Add(eSFX.base1, NativeFile.ReadAllBytes(curdir + #"\Assets\Base.wav"));
}
public Byte[] GetSoundData(eSFX sfx)
{
byte[] output = bank[sfx];
return output;
}
}
In SoundEngine we create a SoundBank object (initialised in SoundEngine constructor):
SoundDataBank soundBank;
public VoiceChannel AddAndPlaySFXFromStore(eSFX sfx, double vol)
{
/**
* sourcevoice will be automatically added to MasteringVoice and engine in the constructor.
*/
byte[] buffer = soundBank.GetSoundData(sfx);
MemoryStream memoryStream = new MemoryStream(buffer);
SoundStream soundStream = new SoundStream(memoryStream);
SourceVoice source = new SourceVoice(engine, soundStream.Format);
AudioBuffer audioBuffer = new AudioBuffer()
{
Stream = soundStream.ToDataStream(),
AudioBytes = (int)soundStream.Length,
Flags = SharpDX.XAudio2.BufferFlags.EndOfStream
};
//Make voice wrapper
VoiceChannel voice = new VoiceChannel(source, engine, MakeOutputMatrix());
//Volume
source.SetVolume((float)vol);
//Play sound
source.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo);
source.Start();
sourceVoices.Add(voice);
return voice;
}
Following this implementation now lets me play up to 20 sound effects - but NOT because we are playing from the soundbank. Infact, even running the old method for sound effects now gets up to 20 sfx instances.
This has improved up to 20 because we have done NativeFile.ReadAllBytes(curdir + #"\Assets\Base.wav") in the constructor for the SoundBank.
I suspect NativeFile is holding a store of loaded file data, so you regardless of whether you run the original SoundEngine.AddAndPlaySFX() or SoundEngine.AddAndPlaySFXFromStore(), they are both running from memory?
Either way, this has quadrupled the limit from before, so this has been incredibly useful - but requires further work.
I'm developing a Windows Phone app that needs to retrieve and manipulate information about the songs played on the device.
I know it is possible to get the song that is currently playing using MediaPlayer.Queue.ActiveSong.
However, what I really need is to have access to a list of recently played tracks.
MediaHistory and MediaHistoryItem classes don't seem to provide this.
Is is really possible? How?
The current API, as #Igor has pointed out in his answer does not allow this. However, there is another way for us to reasonably assume that a particular media file has been played recently, by getting some information about the actual file.
We can use GetBasicPropertiesAsync() along with RetrievePropertiesAsync() which will give us the DateAccessed property for that file.
Here is a code snippet taken from this MSDN page:
public async void test()
{
try
{
StorageFile file = await StorageFile.GetFileFromPathAsync("Filepath");
if (file != null)
{
StringBuilder outputText = new StringBuilder();
// Get basic properties
BasicProperties basicProperties = await file.GetBasicPropertiesAsync();
outputText.AppendLine("File size: " + basicProperties.Size + " bytes");
outputText.AppendLine("Date modified: " + basicProperties.DateModified);
// Specify more properties to retrieve
string dateAccessedProperty = "System.DateAccessed";
string fileOwnerProperty = "System.FileOwner";
List<string> propertiesName = new List<string>();
propertiesName.Add(dateAccessedProperty);
propertiesName.Add(fileOwnerProperty);
// Get the specified properties through StorageFile.Properties
IDictionary<string, object> extraProperties = await file.Properties.RetrievePropertiesAsync(propertiesName);
var propValue = extraProperties[dateAccessedProperty];
if (propValue != null)
{
outputText.AppendLine("Date accessed: " + propValue);
}
propValue = extraProperties[fileOwnerProperty];
if (propValue != null)
{
outputText.AppendLine("File owner: " + propValue);
}
}
}
// Handle errors with catch blocks
catch (FileNotFoundException)
{
// For example, handle a file not found error
}
}
Once you have the DateAccessed property in a variable, we can see if it is a recent date, say, yesterday, or maybe even 2 or 3 days ago. Then we'll know that if it's been accessed within a short period of time, it could have been played.
There are some caveats to this, though. Some virus scanners change the Timestamp properties on files and folders, and they also need to open files to scan them which I would assume would change the DateAccessed property. However, many new Antivirus apps that I've seen revert the Timestamp info back to the original, as if it had never touched the file.
I believe this is the best workaround for this problem at the moment. Unless you only care about when your app recently played a file. Then the answer to that question is as simple as you managing your own recently-played lists for media files.
Update
In order to retrieve the PlayCount for a specified song, you can access that song using the MediaLibrary class:
MediaLibrary library = new MediaLibrary();
Then just access the song like this:
Int32 playCount = library.Songs[0].PlayCount;
where [0] is the index of the song you'd like to get the PlayCount for. An easier way (depending on how you're accessing songs already, might be to do something like:
Int32 playCount = library.Artists[selectedArtistIndex].Albums[selectedArtistAlbumIndex].Songs[selectedSongInAlbumIndex].PlayCount;
Not possible with the current API. MediaHistoryItem only returns last item set by your application, so it is of no use.
Alright. Actually i need mostly the mp4 format. But if it is possible to get for other types as well that would be nice. I just need to read the duration of the file. How can i do that with C# 4.0 ?
So the thing i need is like this video is like : 13 minutes 12 seconds
I can use 3 third party exes too. Like they save the information about the file to a text file. I can parse that text file.
Thank you.
This answer about P/Invoke for Shell32 reminded me of the Windows API Code Pack to access common Windows Vista/7/2008/2008R2 APIs.
It was very easy, using the PropertyEdit demo in the included samples, to figure out the Shell32 API to get various media file properties, like duration.
I assume the same prerequisite applies for having the proper demultiplexers installed, but it was quite simple, as it only required adding references to Microsoft.WindowsAPICodePack.dll and Microsoft.WindowsAPICodePack.Shell.dll and the following code:
using Microsoft.WindowsAPICodePack.Shell;
using Microsoft.WindowsAPICodePack.Shell.PropertySystem;
using (ShellObject shell = ShellObject.FromParsingName(filePath))
{
// alternatively: shell.Properties.GetProperty("System.Media.Duration");
IShellProperty prop = shell.Properties.System.Media.Duration;
// Duration will be formatted as 00:44:08
string duration = prop.FormatForDisplay(PropertyDescriptionFormatOptions.None);
}
Other stuff
Some common properties for an MPEG-4/AAC audio media file:
System.Audio.Format = {00001610-0000-0010-8000-00AA00389B71}
System.Media.Duration = 00:44:08
System.Audio.EncodingBitrate = ?56kbps
System.Audio.SampleRate = ?32 kHz
System.Audio.SampleSize = ?16 bit
System.Audio.ChannelCount = 2 (stereo)
System.Audio.StreamNumber = 1
System.DRM.IsProtected = No
System.KindText = Music
System.Kind = Music
It's easy to iterate through all properties if you're looking for the available metadata:
using (ShellPropertyCollection properties = new ShellPropertyCollection(filePath))
{
foreach (IShellProperty prop in properties)
{
string value = (prop.ValueAsObject == null) ? "" : prop.FormatForDisplay(PropertyDescriptionFormatOptions.None);
Console.WriteLine("{0} = {1}", prop.CanonicalName, value);
}
}
You could also use windows media player, although it don't support alle file types you requested
using WMPLib;
public Double Duration(String file)
{
WindowsMediaPlayer wmp = new WindowsMediaPlayerClass();
IWMPMedia mediainfo = wmp.newMedia(file);
return mediainfo.duration;
}
}
You can use DirectShow API MediaDet object, through DirectShow.NET wrapper library. See Getting length of video for code sample, get_StreamLength gets you the duration in seconds. This assumes Windows has MPEG-4 demultiplexer installed (requires third party components with Windows prior to 7, I believe the same applies to another answer by cezor, there are free to redistribute components though).
IMHO you could use MediaInfo which gives you a lot of information about media files.
There is a CLI for it so you can use it from your code and get info you need.
You can take a look at this link.
I think you are looking for FFMPEG - https://ffmpeg.org/
there are also some free alternatives that you can read about them in this question - Using FFmpeg in .net?
FFMpeg.NET
FFMpeg-Sharp
FFLib.NET
you can see this link for examples of using FFMPEG and finding the duration - http://jasonjano.wordpress.com/2010/02/09/a-simple-c-wrapper-for-ffmpeg/
public VideoFile GetVideoInfo(string inputPath)
{
VideoFile vf = null;
try
{
vf = new VideoFile(inputPath);
}
catch (Exception ex)
{
throw ex;
}
GetVideoInfo(vf);
return vf;
}
public void GetVideoInfo(VideoFile input)
{
//set up the parameters for video info
string Params = string.Format("-i {0}", input.Path);
string output = RunProcess(Params);
input.RawInfo = output;
//get duration
Regex re = new Regex("[D|d]uration:.((\\d|:|\\.)*)");
Match m = re.Match(input.RawInfo);
if (m.Success)
{
string duration = m.Groups[1].Value;
string[] timepieces = duration.Split(new char[] { ':', '.' });
if (timepieces.Length == 4)
{
input.Duration = new TimeSpan(0, Convert.ToInt16(timepieces[0]), Convert.ToInt16(timepieces[1]), Convert.ToInt16(timepieces[2]), Convert.ToInt16(timepieces[3]));
}
}
}
FFMPEG project has a tool, called ffprobe which can provide you the information you need about your multimedia files and ouput the information in a nicely formated JSON.
Take a look at this answer for an example.
Using Windows Media Player Component also, we can get the duration of the video.
Following code snippet may help you guys :
using WMPLib;
// ...
var player = new WindowsMediaPlayer();
var clip = player.newMedia(filePath);
Console.WriteLine(TimeSpan.FromSeconds(clip.duration));
and don't forget to add the reference of wmp.dll which will be
present in System32 folder.
I found the NReco.VideoInfo library to be the best option and far simpler than some of those above. It's a simple as giving the library a file path and it spits out the metadata:
var ffProbe = new FFProbe();
var videoInfo = ffProbe.GetMediaInfo(blob.Uri.AbsoluteUri);
return videoInfo.Duration.TotalMilliseconds;
I had the same problem and we built a wrapper for ffprobe Alturos.VideoInfo.
You can use it simply by installing the nuget package. Also the ffprobe binary is required.
PM> install-package Alturos.VideoInfo
Example
var videoFilePath = "myVideo.mp4";
var videoAnalyer = new VideoAnalyzer("ffprobe.exe");
var analyzeResult = videoAnalyer.GetVideoInfo(videoFilePath);
var duration = analyzeResult.VideoInfo.Format.Duration;
StreamReader errorreader;
string InterviewID = txtToolsInterviewID.Text;
Process ffmpeg = new Process();
ffmpeg.StartInfo.UseShellExecute = false;
ffmpeg.StartInfo.ErrorDialog = false;
ffmpeg.StartInfo.RedirectStandardError = true;
ffmpeg.StartInfo.FileName = Server.MapPath("ffmpeg.exe");
ffmpeg.StartInfo.Arguments = "-i " + Server.MapPath("videos") + "\\226.flv";
ffmpeg.Start();
errorreader = ffmpeg.StandardError;
ffmpeg.WaitForExit();
string result = errorreader.ReadToEnd();
string duration = result.Substring(result.IndexOf("Duration: ") + ("Duration: ").Length, ("00:00:00.00").Length);
I'm trying to rebuild an old metronome application that was originally written using MFC in C++ to be written in .NET using C#. One of the issues I'm running into is playing the midi files that are used to represent the metronome "clicks".
I've found a few articles online about playing MIDI in .NET, but most of them seem to rely on custom libraries that someone has cobbled together and made available. I'm not averse to using these, but I'd rather understand for myself how this is being done, since it seems like it should be a mostly trivial exercise.
So, am I missing something? Or is it just difficult to use MIDI inside of a .NET application?
I'm working on a C# MIDI application at the moment, and the others are right - you need to use p/invoke for this. I'm rolling my own as that seemed more appropriate for the application (I only need a small subset of MIDI functionality), but for your purposes the C# MIDI Toolkit might be a better fit. It is at least the best .NET MIDI library I found, and I searched extensively before starting the project.
I think you'll need to p/invoke out to the windows api to be able to play midi files from .net.
This codeproject article does a good job on explaining how to do this:
vb.net article to play midi files
To rewrite this is c# you'd need the following import statement for mciSendString:
[DllImport("winmm.dll")]
static extern Int32 mciSendString(String command, StringBuilder buffer,
Int32 bufferSize, IntPtr hwndCallback);
Hope this helps - good luck!
midi-dot-net got me up and running in minutes - lightweight and right-sized for my home project. It's also available on GitHub. (Not to be confused with the previously mentioned MIDI.NET, which also looks promising, I just never got around to it.)
Of course NAudio (also mentioned above) has tons of capability, but like the original poster I just wanted to play some notes and quickly read and understand the source code.
I think it's much better to use some library that which has advanced features for MIDI data playback instead of implementing it by your own. For example, with DryWetMIDI (I'm the author of it) to play MIDI file via default synthesizer (Microsoft GS Wavetable Synth):
using Melanchall.DryWetMidi.Devices;
using Melanchall.DryWetMidi.Core;
// ...
var midiFile = MidiFile.Read("Greatest song ever.mid");
using (var outputDevice = OutputDevice.GetByName("Microsoft GS Wavetable Synth"))
{
midiFile.Play(outputDevice);
}
Play will block the calling thread until entire file played. To control playback of a MIDI file, obtain Playback object and use its Start/Stop methods (more details in the Playback article of the library docs):
var playback = midiFile.GetPlayback(outputDevice);
// You can even loop playback and speed it up
playback.Loop = true;
playback.Speed = 2.0;
playback.Start();
// ...
playback.Stop();
// ...
playback.Dispose();
outputDevice.Dispose();
I can't claim to know much about it, but I don't think it's that straightforward - Carl Franklin of DotNetRocks fame has done a fair bit with it - have you seen his DNRTV?
You can use the media player:
using WMPLib;
//...
WindowsMediaPlayer wmp = new WindowsMediaPlayer();
wmp.URL = Path.Combine(Application.StartupPath ,"Resources/mymidi1.mid");
wmp.controls.play();
For extensive MIDI and Wave manipulation in .NET, I think hands down NAudio is the solution (Also available via NuGet).
A recent addition is MIDI.NET that supports Midi Ports, Midi Files and SysEx.
Sorry this question is a little old now, but the following worked for me (somewhat copied from Win32 - Midi looping with MCISendString):
[DllImport("winmm.dll")]
static extern Int32 mciSendString(String command, StringBuilder buffer, Int32 bufferSize, IntPtr hwndCallback);
public static void playMidi(String fileName, String alias)
{
mciSendString("open " + fileName + " type sequencer alias " + alias, new StringBuilder(), 0, new IntPtr());
mciSendString("play " + alias, new StringBuilder(), 0, new IntPtr());
}
public static void stopMidi(String alias)
{
mciSendString("stop " + alias, null, 0, new IntPtr());
mciSendString("close " + alias, null, 0, new IntPtr());
}
A full listing of command strings is given here. The cool part about this is you can just use different things besides sequencer to play different things, say waveaudio for playing .wav files. I can't figure out how to get it to play .mp3 though.
Also, note that the stop and close command must be sent on the same thread that the open and play commands were sent on, otherwise they will have no effect and the file will remain open. For example:
[DllImport("winmm.dll")]
static extern Int32 mciSendString(String command, StringBuilder buffer,
Int32 bufferSize, IntPtr hwndCallback);
public static Dictionary<String, bool> playingMidi = new Dictionary<String, bool>();
public static void PlayMidi(String fileName, String alias)
{
if (playingMidi.ContainsKey(alias))
throw new Exception("Midi with alias '" + alias + "' is already playing");
playingMidi.Add(alias, false);
Thread stoppingThread = new Thread(() => { StartAndStopMidiWithDelay(fileName, alias); });
stoppingThread.Start();
}
public static void StopMidiFromOtherThread(String alias)
{
if (!playingMidi.ContainsKey(alias))
return;
playingMidi[alias] = true;
}
public static bool isPlaying(String alias)
{
return playingMidi.ContainsKey(alias);
}
private static void StartAndStopMidiWithDelay(String fileName, String alias)
{
mciSendString("open " + fileName + " type sequencer alias " + alias, null, 0, new IntPtr());
mciSendString("play " + alias, null, 0, new IntPtr());
StringBuilder result = new StringBuilder(100);
mciSendString("set " + alias + " time format milliseconds", null, 0, new IntPtr());
mciSendString("status " + alias + " length", result, 100, new IntPtr());
int midiLengthInMilliseconds;
Int32.TryParse(result.ToString(), out midiLengthInMilliseconds);
Stopwatch timer = new Stopwatch();
timer.Start();
while(timer.ElapsedMilliseconds < midiLengthInMilliseconds && !playingMidi[alias])
{
}
timer.Stop();
StopMidi(alias);
}
private static void StopMidi(String alias)
{
if (!playingMidi.ContainsKey(alias))
throw new Exception("Midi with alias '" + alias + "' is already stopped");
// Execute calls to close and stop the player, on the same thread as the play and open calls
mciSendString("stop " + alias, null, 0, new IntPtr());
mciSendString("close " + alias, null, 0, new IntPtr());
playingMidi.Remove(alias);
}
A new player emerges:
https://github.com/atsushieno/managed-midi
https://www.nuget.org/packages/managed-midi/
Not much in the way of documentation, but one focus of this library is cross platform support.
System.Media.SoundPlayer is a good, simple way of playing WAV files. WAV files have some advantages over MIDI, one of them being that you can control precisely what each instrument sounds like (rather than relying on the computer's built-in synthesizer).