Skip to content

Commit

Permalink
Merge pull request #2318 from ascpixi/cosmos-audio-infrastructure
Browse files Browse the repository at this point in the history
Cosmos Audio Infrastructure (CAI)
  • Loading branch information
valentinbreiz committed Jul 8, 2022
2 parents 545d960 + 79e2c71 commit ea6350b
Show file tree
Hide file tree
Showing 44 changed files with 2,520 additions and 912 deletions.
81 changes: 81 additions & 0 deletions Docs/articles/Kernel/Audio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# The Cosmos Audio Infrastructure (CAI)
The Cosmos Audio Infrastructure allows for audio manipulation/conversion, audio I/O, and communication between audio devices. The CAI was designed with simplicity and versatility in mind.

A basic example of playing audio through an AC97-compatible audio card:
```cs
var mixer = new AudioMixer();
var audioStream = MemoryAudioStream.FromWave(sampleAudioBytes);
var driver = AC97.Initialize(bufferSize: 4096);
mixer.Streams.Add(audioStream);

var audioManager = new AudioManager()
{
Stream = mixer,
Output = driver
};
audioManager.Enable();
```

## Audio Streams
An `AudioStream` is an object that can provide sample data to audio buffers. By design, the base `AudioStream` class does not have any length or position properties, as audio streams may be infinite - for example, an output stream from a microphone, or an audio mixer. All seekable streams inherit from the class `SeekableAudioStream`, which provides functionality for accessing the position/length properties and allows methods to determine whether they accept infinite and finite streams, or only finite streams.

### Reading audio streams from memory
You can create seekable audio streams from byte arrays using the `MemoryAudioStream` class:
```cs
byte[] bytes = GetArrayOfAudioSamplesFromSomewhere();
var memAudioStream = new MemoryAudioStream(new SampleFormat(AudioBitDepth.Bits16, 2, true), 48000, bytes);
```

However, usually, you will have an audio file which contains a header containing information about the format of the audio samples it contains. The MemoryAudioStream class features support for the [Waveform Audio File Format (WAVE)](https://en.wikipedia.org/wiki/WAV), commonly used with the .WAV extension. To create an memory audio stream from a .WAV file, simply do:
```cs
byte[] bytes = GetWavFileFromSomewhere();
var wavAudioStream = MemoryAudioStream.FromWave(bytes);
```
The method will parse the file and return a `MemoryAudioStream`. The sample format will be determined by using the .WAV header. Please keep in mind that this method only accepts uncompressed LPCM samples, which is the most common encoding used in .WAV files.

## Audio Mixing
The CAI includes an `AudioMixer` class out of the box. This class is an infinite `AudioStream` that mixes given streams together. Please keep in mind that mixing several audio streams together can result in [signal clipping](https://en.wikipedia.org/wiki/Clipping_(signal_processing)). In order to prevent clipping, it's recommended to either decrease the volume of the processed streams by using the `GainPostProcessor`, or implementing your own [audio limiter](https://en.wikipedia.org/wiki/Limiter).

## Audio Buffers
Audio buffers are commonly used in both hardware and software handling - for this reason, the `AudioBuffer` class exists to operate over an array of raw audio sample data.

### Audio Buffer R/W
Audio buffers can be easily written to or read from with the help of the `AudioBufferWriter` or `AudioBufferReader` classes, respectively. These classes automatically perform all bit-depth, channel, and sign conversions. Please keep in mind that conversion operations may be taxing on the CPU. It is recommended to use standard signed 16-bit PCM samples, but, if a conversion operation is necessary, it's recommended to perform them offline (as in, before feeding the unconverted streams into an audio mixer). The reason behind this is because processing the samples within a continously running audio driver will introduce audio crackle if the CPU cannot keep up with the conversion task.

## Audio Post-Processing
Audio streams can be processed before they write to an audio buffer by using the `PostProcessors` property on an `AudioStream` instance. Post-processing effects are simple to implement:

```cs
public class SilencePostProcessor : AudioPostProcessor {
public override void Process(AudioBuffer buffer){
Array.Clear(buffer.RawData);
}
}
```

The above example implements an audio post-processor that turns any audio stream into silence. A more complex example can be seen in the `GainPostProcessor` class, included with the CAI.

## Interfacing with hardware
All hardware interfacing is abstracted behind the `AudioDriver` class. It's recommended to operate an audio driver using the `AudioManager` class. Implementations of the `AudioDriver` class usually do not have a public constructor, as they can handle only one instance of an audio card - if that is the case, they should feature a static `Initialize` method and a static `Instance` property.

For example, to initialize the AC97 driver:
```cs
var driver = AC97.Initialize(4096);
```

As you can see in the example above, the AC97 initialization method accepts an integer parameter - this is the buffer size the AC97 will use. A higher buffer size will result in a decreased amount of clicks and will usually decrease mixing overhead, however, it will increase latency. Some drivers, like the AC97 driver, include support for changing the buffer size while it is running - however, support for this is not guaranteed.

After initializing a driver, it's recommended to handle it using `AudioManager`:
```cs
var audioManager = new AudioManager()
{
Stream = mixer,
Output = driver
};

audioManager.Enable();
```
The audio manager accepts a `Stream` and an `Output` property - the `Stream` is the audio stream that the audio manager will read samples from, which will in turn be provided to the underlying `Output` audio driver. The audio manager abstracts all hardware handling - however, if you need more control over the devices, you can use the driver classes directly.

> **Note**<br>
- > When interfacing with audio devices, remember not to overload the system when supplying the audio samples. When mixing several streams of audio of different formats, for example, the system can get too overloaded, and this will result in audio crackle, or the system won't be able to respond to the audio device in time, resulting in the audio device stopping all output unexpectedly.
1 change: 1 addition & 0 deletions Docs/articles/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
## [Network](Kernel/Network.md)
## [Resource Files](Kernel/ManifestResouceStream.md)
## [Memory Management and Garbage Collection](Kernel/MemoryManagement.md)
## [Audio](Kernel/Audio.md)

# Reference
## [x86](Reference/x86.md)
Expand Down
19 changes: 17 additions & 2 deletions Test.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29728.190
# Visual Studio Version 17
VisualStudioVersion = 17.2.32602.215
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{DAEF99B5-22F0-4885-B45B-9B600B857E1C}"
EndProject
Expand Down Expand Up @@ -172,6 +172,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DapperExtensions.StrongName
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resources", "Resources", "{A45F0D24-1AF3-42BC-91A6-0262AFB1234D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioTests", "Tests\Kernels\AudioTests\AudioTests.csproj", "{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -889,6 +891,18 @@ Global
{AC45D5B2-0D02-49B8-A88E-EABF34AE62B5}.TEST|Any CPU.Build.0 = Debug|Any CPU
{AC45D5B2-0D02-49B8-A88E-EABF34AE62B5}.TEST|x86.ActiveCfg = Debug|Any CPU
{AC45D5B2-0D02-49B8-A88E-EABF34AE62B5}.TEST|x86.Build.0 = Debug|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.Debug|x86.ActiveCfg = Debug|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.Debug|x86.Build.0 = Debug|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.Release|Any CPU.Build.0 = Release|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.Release|x86.ActiveCfg = Release|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.Release|x86.Build.0 = Release|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.TEST|Any CPU.ActiveCfg = TEST|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.TEST|Any CPU.Build.0 = TEST|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.TEST|x86.ActiveCfg = TEST|Any CPU
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C}.TEST|x86.Build.0 = TEST|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -970,6 +984,7 @@ Global
{30D9FA9C-0B4D-40FF-8903-6B9E9C825729} = {ECEA7778-E786-4317-90B9-A2D4427CB91C}
{96855A39-A96B-4BDB-A6AE-29676DFEF637} = {29EEC029-6A2B-478A-B6E5-D63A91388ABA}
{AC45D5B2-0D02-49B8-A88E-EABF34AE62B5} = {A45F0D24-1AF3-42BC-91A6-0262AFB1234D}
{8455DCAE-275E-47B3-B89B-2D9F3AB9977C} = {29EEC029-6A2B-478A-B6E5-D63A91388ABA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4418C803-277E-448F-A0A0-52788FA215AD}
Expand Down
1 change: 1 addition & 0 deletions Tests/Cosmos.TestRunner.Full/Cosmos.TestRunner.Full.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<ProjectReference Include="..\Cosmos.TestRunner.Core\Cosmos.TestRunner.Core.csproj" />
<ProjectReference Include="$(IL2CPURepoRoot)source\Cosmos.IL2CPU\Cosmos.IL2CPU.csproj" />
<ProjectReference Include="$(IL2CPURepoRoot)source\IL2CPU\IL2CPU.csproj" ReferenceOutputAssembly="False" />
<ProjectReference Include="..\Kernels\AudioTests\AudioTests.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Tests/Cosmos.TestRunner.Full/TestKernelSets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ public static IEnumerable<Type> GetStableKernelTypes()
yield return typeof(Kernel.Tests.DiskManager.Kernel);

//yield return typeof(KernelGen3.Boot);

yield return typeof(GraphicTest.Kernel);
yield return typeof(NetworkTest.Kernel);
yield return typeof(AudioTests.Kernel);
// Please see the notes on the kernel itself before enabling it
//yield return typeof(ConsoleTest.Kernel);
// This is a bit slow and works only because ring check is disabled to decide if leave it enabled
Expand Down
17 changes: 17 additions & 0 deletions Tests/Kernels/AudioTests/AudioTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<SupportsX86Intrinsics>False</SupportsX86Intrinsics>
<Configurations>Debug;Release;TEST</Configurations>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\source\Cosmos.Debug.Kernel\Cosmos.Debug.Kernel.csproj" />
<ProjectReference Include="..\..\..\source\Cosmos.System2\Cosmos.System2.csproj" />
<ProjectReference Include="..\..\Cosmos.TestRunner.TestController\Cosmos.TestRunner.TestController.csproj" />
<ProjectReference Include="..\Cosmos.Compiler.Tests.Bcl\Cosmos.Compiler.Tests.Bcl.csproj" />
</ItemGroup>

</Project>
221 changes: 221 additions & 0 deletions Tests/Kernels/AudioTests/Kernel.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion source/Cosmos.Core/PIC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public PIC()
//Init(Master, 0x20, 4, 0xFD | 0x08);
//Init(Slave, 0x28, 2, 0xFF);
//for now enable keyboard, mouse(ps2)
Remap(0x20, 0xF8 | 0x08, 0x28, 0xEB);
Remap(0x20, 0xF8 | 0x08, 0x28, 0xE3);
}

/// <summary>
Expand Down

0 comments on commit ea6350b

Please sign in to comment.