Audio Playback C# is a powerful and versatile tool for managing audio playback using the Windows Multimedia API. This application provides a comprehensive set of features for playing audio, making it an essential resource for developers and enthusiasts alike.
-
Simultaneous Playback: Harness the full potential of the Windows Multimedia API to play multiple audio files simultaneously, allowing for rich and immersive audio experiences.
-
Volume Control: Customize the volume levels of individual audio tracks with precision, ensuring an optimal audio balance tailored to your specific requirements.
-
Looping and Overlapping: Seamlessly loop audio tracks and play overlapping sounds, enabling the creation of captivating and dynamic audio compositions.
-
MCI Integration: Leverage the power of the Media Control Interface (MCI) to interact with multimedia devices, providing a standardized and platform-independent approach to controlling multimedia hardware.
-
User-Friendly Interface: Enjoy a user-friendly and intuitive interface, designed to streamline the process of managing and controlling audio playback operations.
With its robust functionality and seamless integration with the Windows Multimedia API, this application empowers users to create engaging multimedia applications with ease. Whether you are a seasoned developer or an aspiring enthusiast, the Audio Playback Application is your gateway to unlocking the full potential of audio playback on the Windows platform.
Clone the repository now and embark on a transformative audio playback experience! Let's dive into the world of audio together!
In this walkthrough, we will break down the code that implements an AudioPlayer
struct and a Form1
class to manage audio playback.
using System.Runtime.InteropServices;
using System.Text;
using System.Diagnostics;
In this example, we are importing:
System.Runtime.InteropServices
System.Text
System.Diagnostics
This line imports theSystem.Diagnostics
namespace, which provides classes for debugging and tracing. It allows us to print debug messages to the console.
namespace Audio_Playback_CS
- Here, we define a namespace called
Audio_Playback_CS
. Namespaces are used to organize code and avoid naming conflicts with other parts of the program.
public struct AudioPlayer
- Struct Definition: The
AudioPlayer
struct is defined to encapsulate the functionalities related to audio playback.
[DllImport("winmm.dll", EntryPoint = "mciSendStringW")]
private static extern int mciSendStringW([MarshalAs(UnmanagedType.LPTStr)] string lpszCommand,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpszReturnString,
uint cchReturn, IntPtr hwndCallback);
- DllImport: This attribute allows us to call functions from unmanaged libraries. Here, we are importing
mciSendStringW
fromwinmm.dll
, which is used for multimedia control. - Parameters:
lpszCommand
: The command string to send to the MCI (Media Control Interface).lpszReturnString
: A StringBuilder to store the return string from the command.cchReturn
: The size of the return string.hwndCallback
: A handle to a callback window (not used here).
private string[]? Sounds;
This declares an array named Sounds
to store the names of sounds that have been added.
public bool AddSound(string SoundName, string FilePath)
This method adds a sound to the player. It takes the name of the sound and the path to the sound file as parameters.
if (!string.IsNullOrWhiteSpace(SoundName) && File.Exists(FilePath))
Checks if the sound name is not empty or whitespace and if the file exists.
string CommandOpen = $"open \"{FilePath}\" alias {SoundName}";
Creates a command string to open the sound file and assign it an alias.
The escape character \
is used to include special characters in a string. In this case, the escape sequence \"
allows you to include a double quote within a string that's also enclosed in double quotes.
Here's why it's needed: If your file path has spaces, it needs to be enclosed in quotes when you use it in commands. Without escaping the quotes, the string would get cut off at the first double quote it encounters.
For example, let's say your file path is C:\My Files\file.wav
.
- Without escaping:
string CommandOpen = $"open "{FilePath}" alias {SoundName}";
would cause an error because the quotes are not properly handled. - With escaping:
string CommandOpen = $"open \"{FilePath}\" alias {SoundName}";
ensures that the quotes are included as part of the string, making itopen "C:\My Files\file.wav" alias SoundAlias
.
This way, the entire file path is correctly recognized even if it contains spaces, and the command will execute as expected.
if (Sounds == null)
Checks if the Sounds
array is uninitialized.
if (SendMciCommand(CommandOpen, IntPtr.Zero))
Sends the command to open the sound file.
Sounds = new string[1];
Sounds[0] = SoundName;
return true;
Initializes the Sounds
array with the new sound and returns True
.
else if (!Sounds.Contains(SoundName))
Checks if the sound is not already in the array.
Array.Resize(ref Sounds, Sounds.Length + 1);
Sounds[Sounds.Length - 1] = SoundName;
return true;
Adds the new sound to the Sounds
array and returns True
.
Debug.Print($"The sound was not added {SoundName}");
return false;
Prints a debug message and returns False
if the sound could not be added.
public bool SetVolume(string SoundName, int Level)
{
if (Sounds != null && Sounds.Contains(SoundName) && Level >= 0 && Level <= 1000)
{
string CommandVolume = $"setaudio {SoundName} volume to {Level}";
return SendMciCommand(CommandVolume, IntPtr.Zero);
}
Debug.Print($"The volume was not set {SoundName}");
return false;
}
- Method
SetVolume
:- Checks if
Sounds
is not null, if the sound exists, and if the volume level is within the valid range (0 to 1000). - Constructs a command to set the audio volume for the specified sound.
- Sends the command using
SendMciCommand
and returns the result. - Logs a message and returns
false
if the conditions are not met.
- Checks if
public bool LoopSound(string SoundName)
{
if (Sounds != null && Sounds.Contains(SoundName))
{
string CommandSeekToStart = $"seek {SoundName} to start";
string CommandPlayRepeat = $"play {SoundName} repeat";
return SendMciCommand(CommandSeekToStart, IntPtr.Zero) &&
SendMciCommand(CommandPlayRepeat, IntPtr.Zero);
}
Debug.Print($"The sound is not looping {SoundName}");
return false;
}
- Method
LoopSound
:- Checks if the sound exists in the
Sounds
array. - Constructs commands to seek to the start of the sound and play it in repeat mode.
- Sends both commands and returns
true
if successful; otherwise, logs a message and returnsfalse
.
- Checks if the sound exists in the
private bool PlaySound(string SoundName)
{
if (Sounds != null && Sounds.Contains(SoundName))
{
string CommandSeekToStart = $"seek {SoundName} to start";
string CommandPlay = $"play {SoundName} notify";
return SendMciCommand(CommandSeekToStart, IntPtr.Zero) &&
SendMciCommand(CommandPlay, IntPtr.Zero);
}
Debug.Print($"The sound is not playing {SoundName}");
return false;
}
- Method
PlaySound
:- Similar to
LoopSound
, but constructs commands to play the sound once. - Uses
notify
to allow the program to receive notification when the sound finishes playing. - Returns
true
if the commands were successful; otherwise, it logs a message and returnsfalse
.
- Similar to
public bool PauseSound(string SoundName)
{
if (Sounds != null && Sounds.Contains(SoundName))
{
string CommandPause = $"pause {SoundName} notify";
return SendMciCommand(CommandPause, IntPtr.Zero);
}
Debug.Print($"The sound is not paused {SoundName}");
return false;
}
- Method
PauseSound
:- Checks if the sound exists.
- Constructs a command to pause the sound and sends it.
- Returns
true
if successful; otherwise, logs a message and returnsfalse
.
public void AddOverlapping(string SoundName, string FilePath)
{
foreach (string Suffix in new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L" })
{
AddSound(SoundName + Suffix, FilePath);
}
}
- Method
AddOverlapping
:- Adds multiple sounds with suffixes (A to L) to allow overlapping playback.
- Calls
AddSound
for each suffixed name.
public void PlayOverlapping(string SoundName)
{
foreach (string Suffix in new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L" })
{
if (!IsPlaying(SoundName + Suffix))
{
PlaySound(SoundName + Suffix);
return;
}
}
}
- Method
PlayOverlapping
:- Plays the first sound that is not currently playing among the suffixed sounds.
public void SetVolumeOverlapping(string SoundName, int Level)
{
foreach (string Suffix in new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L" })
{
SetVolume(SoundName + Suffix, Level);
}
}
- Method
SetVolumeOverlapping
:- Sets the volume for all suffixed sounds using the
SetVolume
method.
- Sets the volume for all suffixed sounds using the
private bool SendMciCommand(string command, IntPtr hwndCallback)
{
StringBuilder ReturnString = new StringBuilder(128);
try
{
return mciSendStringW(command, ReturnString, 0, hwndCallback) == 0;
}
catch (Exception ex)
{
Debug.Print($"Error: {ex.Message}");
return false;
}
}
- Method
SendMciCommand
:- Sends a command to the MCI and checks for errors.
- Returns
true
if the command was successful; otherwise, logs the error and returnsfalse
.
private string GetStatus(string SoundName, string StatusType)
{
try
{
if (Sounds != null && Sounds.Contains(SoundName))
{
string CommandStatus = $"status {SoundName} {StatusType}";
StringBuilder StatusReturn = new StringBuilder(128);
mciSendStringW(CommandStatus, StatusReturn, 128, IntPtr.Zero);
return StatusReturn.ToString().Trim().ToLower();
}
}
catch (Exception ex)
{
Debug.Print($"Error getting status: {ex.Message}");
}
return string.Empty;
}
- Method
GetStatus
:- Retrieves the status of a sound (e.g., whether it is playing).
- Constructs a status command and returns the result as a string.
public void CloseSounds()
{
if (Sounds != null)
{
foreach (string Sound in Sounds)
{
string CommandClose = $"close {Sound}";
SendMciCommand(CommandClose, IntPtr.Zero);
}
Sounds = null;
}
}
- Closes all open sounds by sending a close command for each sound in the
Sounds
array. Sounds = null;
: This line sets theSounds
array tonull
. By doing this, it effectively clears the reference to the array, ensuring that all resources associated with the sounds are released. It also prevents further usage of the array without reinitializing it, which is a good practice for memory management and avoiding potential errors in your application.
public partial class Form1 : Form
{
private AudioPlayer Player;
private void Form1_Load(object sender, EventArgs e)
{
Text = "Audio Playback CS - Code with Joe";
CreateSoundFiles();
string FilePath = Path.Combine(Application.StartupPath, "level.mp3");
Player.AddSound("Music", FilePath);
Player.SetVolume("Music", 600);
FilePath = Path.Combine(Application.StartupPath, "CashCollected.mp3");
Player.AddOverlapping("CashCollected", FilePath);
Player.SetVolumeOverlapping("CashCollected", 900);
Player.LoopSound("Music");
Debug.Print($"Running... {DateTime.Now}");
}
- Form1 Class: Inherits from
Form
, which is part of Windows Forms for creating GUI applications. - Player Field: An instance of
AudioPlayer
is created to manage audio playback. - Form1_Load Method:
- Sets the form title.
- Calls
CreateSoundFiles
to ensure the sound files exist. - Adds sounds and sets their volumes.
- Loops the background music and logs the current time.
private void Button1_Click(object sender, EventArgs e)
{
Player.PlayOverlapping("CashCollected");
}
private void Button2_Click(object sender, EventArgs e)
{
if (Player.IsPlaying("Music"))
{
Player.PauseSound("Music");
button2.Text = "Play Loop";
}
else
{
Player.LoopSound("Music");
button2.Text = "Pause Loop";
}
}
- Button1_Click: Plays the overlapping "CashCollected" sound when the button is clicked.
- Button2_Click: Toggles between pausing the music and looping it, updating the button text accordingly.
private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Player.CloseSounds();
}
- Form1_Closing: Ensures all sounds are closed when the form is closing.
private void CreateSoundFiles()
{
string filePath = Path.Combine(Application.StartupPath, "level.mp3");
CreateFileFromResource(filePath, Audio_Playback_CS.Resource1.level);
filePath = Path.Combine(Application.StartupPath, "CashCollected.mp3");
CreateFileFromResource(filePath, Audio_Playback_CS.Resource1.CashCollected);
}
- Method
CreateSoundFiles
:- Creates sound files from resources if they do not already exist.
private void CreateFileFromResource(string filePath, byte[] resource)
{
try
{
if (!File.Exists(filePath))
{
File.WriteAllBytes(filePath, resource);
}
}
catch (Exception ex)
{
Debug.Print($"Error creating file: {ex.Message}");
}
}
- Method
CreateFileFromResource
:- Writes byte arrays from resources to files on disk.
- Catches exceptions and logs errors if file creation fails.
To add a resource file to your Visual Studio project, follow these steps:
- Add a New Resource File:
- From the Project menu, select
Add New Item...
. - In the dialog that appears, choose
Resource File
from the list of templates. - Name your resource file (e.g.,
Resource1.resx
) and clickAdd
.
- From the Project menu, select
- Open the Resource Editor:
- Double-click the newly created
.resx
file to open the resource editor.
- Double-click the newly created
- Add Existing Files:
- In the resource editor, click on the Green Plus Sign or right-click in the resource pane and select
Add Resource
. - Choose
Add Existing File...
from the context menu. - Navigate to the location of the MP3 file (or any other resource file) you want to add, select it, and click
Open
.
- In the resource editor, click on the Green Plus Sign or right-click in the resource pane and select
-
Verify the Addition:
- Ensure that your MP3 file appears in the list of resources in the resource editor. It should now be accessible via the Resource class in your code.
-
Accessing the Resource in Code:
- You can access the added resource in your code using the following syntax:
CreateFileFromResource(filePath, YourProjectNamespace.Resource1.YourResourceName); // Example CreateFileFromResource(filePath, Resource1.CashCollected);
- You can access the added resource in your code using the following syntax:
-
Save Changes:
- Don’t forget to save your changes to the
.resx
file.
- Don’t forget to save your changes to the
By following these steps, you can easily add any existing MP3 file or other resources to your Visual Studio project and utilize them within your Audio Playback application.
This project serves as a direct port of the original Audio Playback project created in VB.NET, which you can also explore for a different perspective on the same concepts. For more information and to access the complete code, visit the Audio Playback Repository and the Audio Playback C# Repository. Happy coding!
This code provides a comprehensive example of how to create an audio playback application in C#. We covered everything from adding sounds to managing their playback and volume. By understanding each part of this code, you can build a solid foundation for working with audio in your applications.
Feel free to experiment with the code and modify it to enhance your learning experience!