Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
obj
bin
installer/Output
# Explicitly ignore debug/release .pdb files for extra safety
bin/Debug/net6.0-windows/FlexASIOGUI.pdb
bin/Release/net6.0-windows/FlexASIOGUI.pdb
# Release builds do not include .pdb files by default, but this ensures no accidental inclusion
20 changes: 19 additions & 1 deletion FlexASIOGUI.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
Expand All @@ -8,6 +8,10 @@
<ApplicationIcon>installer\flexasiogui.ico</ApplicationIcon>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>none</DebugType>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
Expand All @@ -20,4 +24,18 @@
</Reference>
</ItemGroup>

<ItemGroup>
<Content Include="lib\portaudio_x64.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>portaudio_x64.dll</Link>
</Content>
</ItemGroup>

<ItemGroup>
<Content Include="lib\portaudio_x64.pdb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>portaudio_x64.pdb</Link>
</Content>
</ItemGroup>

</Project>
65 changes: 62 additions & 3 deletions Form1.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
Expand All @@ -8,6 +8,7 @@
using System.Diagnostics;
using System.IO;
using System.Globalization;
// Tomlyn is used instead of deprecated Nett library for TOML parsing (migrated in v0.35)
using Tomlyn;
using System.Runtime.InteropServices;

Expand All @@ -24,13 +25,61 @@ public partial class Form1 : Form
private readonly string flexasioVersion = "1.9";
private readonly string tomlName = "FlexASIO.toml";
private readonly string docUrl = "https://github.com/dechamps/FlexASIO/blob/master/CONFIGURATION.md";
// Tomlyn library options for TOML serialization/deserialization
TomlModelOptions tomlModelOptions = new();

[DllImport(@"C:\Program Files\FlexASIO\x64\FlexASIO.dll")]
public static extern int Initialize(string PathName, bool TestMode);
[DllImport(@"kernel32.dll")]
public static extern uint GetACP();

// Direct PortAudio P/Invoke declarations for UTF-8 string handling
[DllImport("portaudio_x64.dll")]
private static extern IntPtr Pa_GetDeviceInfo(int device);

[StructLayout(LayoutKind.Sequential)]
private struct PaDeviceInfo
{
public int structVersion;
public IntPtr name; // const char* - UTF-8 string
public int hostApi;
public int maxInputChannels;
public int maxOutputChannels;
public double defaultLowInputLatency;
public double defaultLowOutputLatency;
public double defaultHighInputLatency;
public double defaultHighOutputLatency;
public double defaultSampleRate;
}

// Helper method to safely get device name as UTF-8
private static string GetDeviceNameUTF8(int deviceIndex)
{
try
{
IntPtr deviceInfoPtr = Pa_GetDeviceInfo(deviceIndex);
if (deviceInfoPtr == IntPtr.Zero)
return string.Empty;

PaDeviceInfo deviceInfo = Marshal.PtrToStructure<PaDeviceInfo>(deviceInfoPtr);
if (deviceInfo.name == IntPtr.Zero)
return string.Empty;

// Read the string as UTF-8
int length = 0;
while (Marshal.ReadByte(deviceInfo.name, length) != 0)
length++;

byte[] buffer = new byte[length];
Marshal.Copy(deviceInfo.name, buffer, 0, length);
return Encoding.UTF8.GetString(buffer);
}
catch
{
return string.Empty;
}
}

public Form1()
{
InitializeComponent();
Expand All @@ -51,6 +100,7 @@ public Form1()

TOMLPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}\\{tomlName}";

// Keep C# property names as-is when serializing/deserializing TOML (no case conversion)
tomlModelOptions.ConvertPropertyName = (string name) => name;
this.LoadFlexASIOConfig(TOMLPath);

Expand Down Expand Up @@ -142,18 +192,27 @@ private TreeNode[] GetDevicesForBackend(string Backend, bool Input)
if (apiInfo.name != Backend)
continue;

// Use direct P/Invoke to get UTF-8 device name
string deviceName = GetDeviceNameUTF8(i);

// Fallback to the old method if P/Invoke fails
if (string.IsNullOrEmpty(deviceName))
{
deviceName = DescrambleUTF8(deviceInfo.name);
}

if (Input == true)
{
if (deviceInfo.maxInputChannels > 0)
{
treeNodes.Add(new TreeNode(DescrambleUTF8(deviceInfo.name)));
treeNodes.Add(new TreeNode(deviceName));
}
}
else
{
if (deviceInfo.maxOutputChannels > 0)
{
treeNodes.Add(new TreeNode(DescrambleUTF8(deviceInfo.name)));
treeNodes.Add(new TreeNode(deviceName));
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions docs/CHANGELOG_RUTICE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Change Log (Rutice/ruticejp)

This document summarizes the key changes and maintenance actions performed by Rutice ([ruticejp](https://github.com/ruticejp)) for this repository.

---

## 2025-11-19

- Changed default git branch from `master` to `main` and deleted the old `master` branch.
- Created branch `fix-utf8-encoding` for minimal UTF-8 encoding fixes (Japanese audio device names).
- Added English documentation: `dotnet-eol.md` (.NET 6.0 EOL notice and migration recommendations), `fix-utf8-encoding.md` (Purpose and scope of UTF-8 encoding fix branch), `ENCODING_FIX.md` (Details of the UTF-8 encoding fix implementation)
- Added supplementary notes to documentation files, clarifying authorship and contact info.
- Built Debug and Release configurations, confirmed .pdb exclusion in Release builds.
- Updated `.gitignore` to explicitly exclude `.pdb` files for Debug/Release builds.
- Configured `.csproj` to prevent `.pdb` generation in Release builds.
- Cleaned up `bin` directory and rebuilt both Debug and Release outputs.

---

*This changelog was created and maintained by Rutice ([ruticejp](https://github.com/ruticejp)).*
153 changes: 153 additions & 0 deletions docs/ENCODING_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# UTF-8 Encoding Fix for Audio Device Names

## Problem

The original FlexASIO GUI had character encoding issues when displaying audio device names containing non-ASCII characters. Specifically:

- **Japanese** device names like `スピーカー` (Speaker) and `マイク` (Microphone) were displayed as garbled text: `繝槭う繧ッ`, `繧ケ繝斐・繧ォ繝シ`
- This issue affected all languages using non-ASCII characters

## Root Cause

The problem was caused by the `portaudio-sharp` wrapper library incorrectly interpreting UTF-8 encoded strings from the native PortAudio C library:

1. PortAudio C API returns device names as UTF-8 encoded `const char*` strings
2. The `portaudio-sharp` managed wrapper uses default marshaling, which interprets the strings as ANSI (system code page)
3. On Japanese Windows (CP932/Shift-JIS), UTF-8 bytes were misinterpreted as Shift-JIS, causing mojibake (garbled text)

## Solution

We implemented a **P/Invoke (Platform Invocation Services)** approach to directly call the native PortAudio API and correctly decode UTF-8 strings:

### Implementation

```csharp
// Direct P/Invoke declaration to PortAudio C API
[DllImport("portaudio_x64.dll")]
private static extern IntPtr Pa_GetDeviceInfo(int device);

[StructLayout(LayoutKind.Sequential)]
private struct PaDeviceInfo
{
public int structVersion;
public IntPtr name; // const char* - UTF-8 string
public int hostApi;
public int maxInputChannels;
public int maxOutputChannels;
public double defaultLowInputLatency;
public double defaultLowOutputLatency;
public double defaultHighInputLatency;
public double defaultHighOutputLatency;
public double defaultSampleRate;
}

// Helper method to safely read UTF-8 device names
private static string GetDeviceNameUTF8(int deviceIndex)
{
try
{
IntPtr deviceInfoPtr = Pa_GetDeviceInfo(deviceIndex);
if (deviceInfoPtr == IntPtr.Zero)
return string.Empty;

PaDeviceInfo deviceInfo = Marshal.PtrToStructure<PaDeviceInfo>(deviceInfoPtr);
if (deviceInfo.name == IntPtr.Zero)
return string.Empty;

// Read the string as UTF-8
int length = 0;
while (Marshal.ReadByte(deviceInfo.name, length) != 0)
length++;

byte[] buffer = new byte[length];
Marshal.Copy(deviceInfo.name, buffer, 0, length);
return Encoding.UTF8.GetString(buffer); // Correct UTF-8 decoding
}
catch
{
return string.Empty;
}
}
```

### Key Points

1. **Direct API Call**: Bypasses the `portaudio-sharp` wrapper entirely
2. **Manual UTF-8 Decoding**: Reads raw bytes and uses `Encoding.UTF8.GetString()` for proper decoding
3. **Fallback Support**: Falls back to the original `DescrambleUTF8` method if P/Invoke fails

## Language Support

This fix supports **all languages that use non-ASCII characters**, including but not limited to:

### Verified Languages

- ✅ **Japanese** (日本語) - e.g., `スピーカー`, `マイク`

### Expected to Work

- ✅ **Korean** (한국어) - e.g., `스피커`, `마이크`
- ✅ **Simplified Chinese** (简体中文) - e.g., `扬声器`, `麦克风`
- ✅ **Traditional Chinese** (繁體中文) - e.g., `揚聲器`, `麥克風`
- ✅ **Cyrillic** (Русский) - e.g., `Динамики`, `Микрофон`
- ✅ **Arabic** (العربية) - RTL (right-to-left) text
- ✅ **Hebrew** (עברית) - RTL (right-to-left) text
- ✅ **Thai** (ไทย) - Complex script
- ✅ **Greek** (Ελληνικά)
- ✅ **All other Unicode-supported languages**

### Technical Reason

Since the fix uses standard UTF-8 decoding (`Encoding.UTF8.GetString()`), it supports **all Unicode character sets** (covering 149+ writing systems). UTF-8 is a universal encoding that can represent any Unicode character.

## Before and After

### Before (Garbled Japanese Text)

```text
Device: 繝槭う繧ッ
Device: 繧ケ繝斐・繧ォ繝シ
Device: 繧ケ繝・Ξ繧ェ 繝溘く繧オ繝シ
```

### After (Correct Japanese Text)

```text
Device: マイク (Microphone)
Device: スピーカー (Speaker)
Device: ステレオ ミキサー (Stereo Mixer)
```

## Files Modified

- **Form1.cs**: Added P/Invoke declarations and `GetDeviceNameUTF8()` helper method
- **FlexASIOGUI.csproj**: Fixed `portaudio_x64.dll` auto-copy configuration

## Testing

To verify the fix works on your system:

1. Build the project:

```bash
dotnet build -c Release
```

2. Run the application and check if device names with non-ASCII characters display correctly

3. Test with devices that have names in different languages

## Notes

- This fix maintains backward compatibility with the original `DescrambleUTF8` fallback method
- The P/Invoke approach is preferred as it addresses the root cause rather than working around symptoms
- RTL (right-to-left) languages like Arabic and Hebrew are supported for text storage and retrieval, though UI display may require additional Windows Forms RTL settings

## References

- [PortAudio API Documentation](http://www.portaudio.com/docs/v19-doxydocs/portaudio_8h.html)
- [UTF-8 Encoding](https://en.wikipedia.org/wiki/UTF-8)
- [P/Invoke in .NET](https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke)

---
*Supplementary note by Rutice ([ruticejp](https://github.com/ruticejp))*
35 changes: 35 additions & 0 deletions docs/dotnet-eol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# .NET Target Framework EOL (End Of Life) Notice

## Current Status

This project currently targets `net6.0-windows` as its framework.

- `net6.0-windows` reached End Of Life (EOL) in November 2024.
- No further security updates or bug fixes will be provided for this version.
- For details, see the official [.NET Support Policy](https://aka.ms/dotnet-core-support).

## Recommended Actions

To ensure continued security and stability, it is recommended to:

1. **Migrate to the latest LTS (Long Term Support) framework**
- Example: `net8.0-windows`
2. **Migration Steps**
- Change `<TargetFramework>` in `FlexASIOGUI.csproj` to `net8.0-windows`
- Check compatibility of dependencies and APIs
- Build and test the application

## Notes

- Some APIs or behaviors may change in newer frameworks.
- Refer to official documentation and migration guides for details.

## Reference Links

- [.NET Support Policy](https://aka.ms/dotnet-core-support)
- [.NET 8.0 Release Notes](https://learn.microsoft.com/dotnet/core/whats-new/dotnet-8)
- [Migration Guide](https://learn.microsoft.com/dotnet/core/porting/)

---

*Supplementary note by Rutice ([ruticejp](https://github.com/ruticejp))*
20 changes: 20 additions & 0 deletions docs/fix-utf8-encoding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# About the `fix-utf8-encoding` Branch

This branch provides minimal support for UTF-8 encoding issues, specifically addressing garbled characters in Japanese audio device names.

## Purpose

- The main goal is to fix character corruption (mojibake) for Japanese device names in the application UI.
- Only essential changes for UTF-8 handling are included; no extensive refactoring or additional features are implemented.

## Notes

- This is not a comprehensive solution for all encoding problems.
- Further improvements or broader encoding support may be addressed in future branches.

---

**This branch is intended for quick and minimal UTF-8 encoding fixes.**

---
*Supplementary note by Rutice ([ruticejp](https://github.com/ruticejp))*