top of page

- Detail -

RADNODZのUnreal Engine 5用プラグインを使用してRIGDOCKSのAPIを使用し、Reaperを操作するアプリを作成します。

本プロジェクトではReaperへの接続、RIGDOCKSのAPIの実行までを行います。

- Images -

- Link -

- Youtube -

- Sources -

RadnodzDemoWidget.h

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "RadnodzDemoWidget.generated.h"

class UButton;
class UEditableTextBox;
class UTextBlock;
class URadnodzClient;

/**
 * REAPER に接続し、選択中のメディアアイテムのうち 1 つ目の名前を変更するデモ画面。
 */
UCLASS()
class REAPERCONTROLLER_API URadnodzDemoWidget : public UUserWidget
{
	GENERATED_BODY()

protected:
    virtual void NativeConstruct() override;
    virtual void NativeDestruct() override;

    // ---- UMG のパーツと結びつける ----
    // ここの変数名は、Widget Blueprint 側のパーツ名と完全に一致させること
    UPROPERTY(meta = (BindWidget))
    TObjectPtr<UEditableTextBox> IpTextBox;

    UPROPERTY(meta = (BindWidget))
    TObjectPtr<UEditableTextBox> PortTextBox;

    UPROPERTY(meta = (BindWidget))
    TObjectPtr<UEditableTextBox> NameTextBox;

    UPROPERTY(meta = (BindWidget))
    TObjectPtr<UButton> RenameButton;

    UPROPERTY(meta = (BindWidget))
    TObjectPtr<UTextBlock> StatusText;

private:
    UFUNCTION()
    void OnRenameClicked();

    /** 未接続なら Connect する。成功したら true。失敗理由は StatusText に出す。 */
    bool EnsureConnected();

    void SetStatus(const FString& Message);

    UPROPERTY()
    TObjectPtr<URadnodzClient> Client;
};

RadnodzDemoWidget.cpp

#include "RadnodzDemoWidget.h"

#include "RadnodzClient.h"                  // URadnodzClient / FRigdocksError
#include "RigdocksError.h"                  // AZ_ErrorCode
#include "RigdocksBronze_MediaItem.h"       // URigdocksBronze_MediaItem

#include "Components/Button.h"
#include "Components/EditableTextBox.h"
#include "Components/TextBlock.h"

namespace
{
    // 選択中のメディアアイテムのうち 1 つ目
    constexpr int FirstSelectedItemID = 0;
}

void URadnodzDemoWidget::NativeConstruct()
{
    Super::NativeConstruct();

    // ボタンが押されたら OnRenameClicked を呼ぶよう登録する
    if (RenameButton)
    {
        RenameButton->OnClicked.AddDynamic(this, &URadnodzDemoWidget::OnRenameClicked);
    }
}

void URadnodzDemoWidget::NativeDestruct()
{
    if (Client && Client->IsConnected())
    {
        Client->Disconnect();
    }

    Super::NativeDestruct();
}

void URadnodzDemoWidget::SetStatus(const FString& Message)
{
    if (StatusText)
    {
        StatusText->SetText(FText::FromString(Message));
    }

    UE_LOG(LogTemp, Log, TEXT("[RADNODZ] %s"), *Message);
}

bool URadnodzDemoWidget::EnsureConnected()
{
    // クライアントは初回だけ生成し、以降は使い回す
    if (!Client)
    {
        Client = URadnodzClient::CreateRadnodzClient();
    }

    if (Client->IsConnected())
    {
        return true;
    }

    if (!IpTextBox || !PortTextBox)
    {
        SetStatus(TEXT("IP / ポートの入力欄が見つかりません"));
        return false;
    }

    const FString Ip = IpTextBox->GetText().ToString().TrimStartAndEnd();
    const FString PortString = PortTextBox->GetText().ToString().TrimStartAndEnd();

    if (Ip.IsEmpty())
    {
        SetStatus(TEXT("IP を入力してください"));
        return false;
    }

    if (!PortString.IsNumeric())
    {
        SetStatus(TEXT("ポートは数字で入力してください"));
        return false;
    }

    const int Port = FCString::Atoi(*PortString);

    Client->Connect(Ip, Port);

    // 成否は戻り値ではなく GetError() で判定する
    const FRigdocksError Error = Client->GetError();
    if (Error.ErrorCode != static_cast<int>(AZ_ErrorCode::Success) || !Client->IsConnected())
    {
        SetStatus(FString::Printf(TEXT("接続失敗 (%d): %s"), Error.ErrorCode, *Error.Message));
        return false;
    }

    return true;
}

void URadnodzDemoWidget::OnRenameClicked()
{
    // ---- 1. クライアントを用意する(初回だけ生成し、以降は使い回す)----
    if (!Client)
    {
        Client = URadnodzClient::CreateRadnodzClient();
    }

    // ---- 2. 接続する ----
    const FString Ip = IpTextBox->GetText().ToString();
    const FString Port = PortTextBox->GetText().ToString();

    if (!Client->Connect(Ip, FCString::Atoi(*Port)))
    {
        const FRigdocksError Error = Client->GetError();
        SetStatus(FString::Printf(TEXT("接続失敗 (%d): %s"), Error.ErrorCode, *Error.Message));
        return;
    }

    // ---- 3. 名前を設定する ----
    const FString NewName = NameTextBox->GetText().ToString();

    URigdocksBronze_MediaItem::AZ_SetSelectedMediaName(Client, 0, 0, NewName);

    // ---- 4. 成否を確認する ----
    const FRigdocksError Error = Client->GetError();
    if (Error.ErrorCode != 0)
    {
        SetStatus(FString::Printf(TEXT("リネーム失敗 (%d): %s"), Error.ErrorCode, *Error.Message));
        return;
    }

    SetStatus(FString::Printf(TEXT("リネーム成功: %s"), *NewName));
}

RadnodzDemoFromAI.h

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "RadnodzDemoFromAI.generated.h"

class UButton;
class UEditableTextBox;
class UTextBlock;
class UVerticalBox;
class URadnodzClient;

/**
 * REAPER に接続し、選択中のメディアアイテムのうち 1 つ目の名前を変更する画面。
 *
 * ウィジェットの配置も含めてすべて C++ で組み立てるので、
 * Widget Blueprint を作らなくてもこのクラスだけで動く。
 *
 *   UUserWidget* Screen = CreateWidget<URadnodzDemoFromAI>(GetWorld(), URadnodzDemoFromAI::StaticClass());
 *   Screen->AddToViewport();
 */
UCLASS()
class REAPERCONTROLLER_API URadnodzDemoFromAI : public UUserWidget
{
	GENERATED_BODY()

protected:
	/** ここで画面の中身(ウィジェットツリー)を組み立てる。 */
	virtual TSharedRef<SWidget> RebuildWidget() override;

	virtual void NativeConstruct() override;
	virtual void NativeDestruct() override;

private:
	// ---- 画面を組み立てる ----

	/** ウィジェットツリーを一度だけ作る。 */
	void BuildLayout();

	/** 「ラベル + 入力欄」の 1 行を作って Parent に足し、作った入力欄を返す。 */
	UEditableTextBox* AddLabeledTextBox(
		UVerticalBox* Parent,
		const FName& WidgetName,
		const FString& Label,
		const FString& Hint,
		const FString& InitialText);

	// ---- 画面のパーツ(BuildLayout で生成する)----

	/** 接続先 IP (例: 127.0.0.1) */
	UPROPERTY()
	TObjectPtr<UEditableTextBox> IpTextBox;

	/** 接続先ポート */
	UPROPERTY()
	TObjectPtr<UEditableTextBox> PortTextBox;

	/** 付け直す名前 */
	UPROPERTY()
	TObjectPtr<UEditableTextBox> NameTextBox;

	/** 実行ボタン */
	UPROPERTY()
	TObjectPtr<UButton> RunButton;

	/** 進捗・エラーの表示先 */
	UPROPERTY()
	TObjectPtr<UTextBlock> StatusText;

	// ---- 処理 ----

	UFUNCTION()
	void OnRunClicked();

	/**
	 * 入力欄の IP / ポートで接続を用意する。成功したら true。
	 * 失敗した理由は StatusText に出す。
	 */
	bool EnsureConnected();

	/** StatusText に出しつつ、同じ内容をログにも残す。 */
	void SetStatus(const FString& Message);

	/** 接続クライアント。初回に生成して以降は使い回す。 */
	UPROPERTY()
	TObjectPtr<URadnodzClient> Client;

	/** 今つながっている接続先。入力が変わったら張り直すために覚えておく。 */
	FString ConnectedIp;
	int ConnectedPort = 0;
};

RadnodzDemoFromAI.h

#include "RadnodzDemoFromAI.h"

#include "RadnodzClient.h"                  // URadnodzClient / FRigdocksError
#include "RigdocksError.h"                  // AZ_ErrorCode
#include "RigdocksBronze_MediaItem.h"       // URigdocksBronze_MediaItem

#include "Blueprint/WidgetTree.h"
#include "Components/Border.h"
#include "Components/BorderSlot.h"
#include "Components/Button.h"
#include "Components/ButtonSlot.h"
#include "Components/EditableTextBox.h"
#include "Components/HorizontalBox.h"
#include "Components/HorizontalBoxSlot.h"
#include "Components/SizeBox.h"
#include "Components/SizeBoxSlot.h"
#include "Components/TextBlock.h"
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
#include "Styling/CoreStyle.h"

namespace
{
	// 選択中のメディアアイテムのうち 1 つ目
	constexpr int FirstSelectedItemID = 0;

	// ---- 見た目の数値をここにまとめておく ----
	constexpr float PanelWidth = 560.f;   // 画面全体の横幅
	constexpr float LabelWidth = 130.f;   // ラベル列の横幅
	constexpr float RowSpacing = 6.f;     // 行と行のすき間

	FSlateFontInfo TitleFont() { return FCoreStyle::GetDefaultFontStyle("Bold", 18); }
	FSlateFontInfo BodyFont() { return FCoreStyle::GetDefaultFontStyle("Regular", 13); }

	/**
	 * 入力欄の文字を黒にする。
	 * SEditableTextBox は「通常 / 入力中 / 読み取り専用」で参照する色が別々なので、全部そろえる。
	 */
	void ApplyBlackText(UEditableTextBox* TextBox)
	{
		const FSlateColor Black(FLinearColor::Black);

		FEditableTextBoxStyle Style = TextBox->GetWidgetStyle();
		Style.TextStyle.ColorAndOpacity = Black;   // 文字そのものの色
		Style.ForegroundColor = Black;             // 未フォーカス時
		Style.FocusedForegroundColor = Black;      // 入力中
		Style.ReadOnlyForegroundColor = Black;     // 読み取り専用時
		TextBox->SetWidgetStyle(Style);
	}

	/** RADNODZ の呼び出し結果を、そのまま画面に出せる文にする。 */
	FString FormatError(const FRigdocksError& Error)
	{
		if (Error.Details.IsEmpty())
		{
			return FString::Printf(TEXT("(%d) %s"), Error.ErrorCode, *Error.Message);
		}

		return FString::Printf(TEXT("(%d) %s / %s"), Error.ErrorCode, *Error.Message, *Error.Details);
	}

	bool IsSuccess(const FRigdocksError& Error)
	{
		return Error.ErrorCode == static_cast<int>(AZ_ErrorCode::Success);
	}
}

// ============================================================
//  画面の組み立て
// ============================================================

TSharedRef<SWidget> URadnodzDemoFromAI::RebuildWidget()
{
	// RebuildWidget は作り直しのたびに呼ばれうるので、中身が無いときだけ組み立てる
	if (WidgetTree && !WidgetTree->RootWidget)
	{
		BuildLayout();
	}

	return Super::RebuildWidget();
}

void URadnodzDemoFromAI::BuildLayout()
{
	// ---- 一番外側: 画面いっぱいの背景 ----
	UBorder* Background = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("Background"));
	Background->SetBrushColor(FLinearColor(0.02f, 0.02f, 0.03f, 0.85f));
	Background->SetPadding(FMargin(24.f));
	WidgetTree->RootWidget = Background;

	// ---- 中身の横幅を決めて中央に置く ----
	USizeBox* Panel = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass(), TEXT("Panel"));
	Panel->SetWidthOverride(PanelWidth);
	Background->AddChild(Panel);

	if (UBorderSlot* PanelSlot = Cast<UBorderSlot>(Panel->Slot))
	{
		PanelSlot->SetHorizontalAlignment(HAlign_Center);
		PanelSlot->SetVerticalAlignment(VAlign_Center);
	}

	// ---- 縦に積んでいく本体 ----
	UVerticalBox* Root = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("RootBox"));
	Panel->AddChild(Root);

	// 見出し
	UTextBlock* Title = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("Title"));
	Title->SetText(FText::FromString(TEXT("REAPER: 選択中の 1 つ目のメディアアイテムをリネーム")));
	Title->SetFont(TitleFont());

	if (UVerticalBoxSlot* TitleSlot = Root->AddChildToVerticalBox(Title))
	{
		TitleSlot->SetPadding(FMargin(0.f, 0.f, 0.f, 16.f));
	}

	// 入力欄 3 つ
	IpTextBox = AddLabeledTextBox(Root, TEXT("IpTextBox"), TEXT("IP"), TEXT("127.0.0.1"), TEXT("127.0.0.1"));
	PortTextBox = AddLabeledTextBox(Root, TEXT("PortTextBox"), TEXT("ポート"), TEXT("例: 5000"), FString());
	NameTextBox = AddLabeledTextBox(Root, TEXT("NameTextBox"), TEXT("新しい名前"), TEXT("付け直す名前"), FString());

	// 実行ボタン
	RunButton = WidgetTree->ConstructWidget<UButton>(UButton::StaticClass(), TEXT("RunButton"));

	UTextBlock* RunLabel = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("RunLabel"));
	RunLabel->SetText(FText::FromString(TEXT("実行")));
	RunLabel->SetFont(BodyFont());
	RunLabel->SetColorAndOpacity(FSlateColor(FLinearColor::Black));
	RunButton->AddChild(RunLabel);

	if (UButtonSlot* RunLabelSlot = Cast<UButtonSlot>(RunLabel->Slot))
	{
		RunLabelSlot->SetPadding(FMargin(24.f, 8.f));
		RunLabelSlot->SetHorizontalAlignment(HAlign_Center);
		RunLabelSlot->SetVerticalAlignment(VAlign_Center);
	}

	if (UVerticalBoxSlot* ButtonSlot = Root->AddChildToVerticalBox(RunButton))
	{
		ButtonSlot->SetPadding(FMargin(0.f, 16.f, 0.f, 12.f));
		ButtonSlot->SetHorizontalAlignment(HAlign_Right);
	}

	// ここで押されたときの処理をつないでおく(BuildLayout は一度しか通らない)
	RunButton->OnClicked.AddDynamic(this, &URadnodzDemoFromAI::OnRunClicked);

	// 状態・エラーの表示先
	StatusText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("StatusText"));
	StatusText->SetFont(BodyFont());
	StatusText->SetAutoWrapText(true);

	if (UVerticalBoxSlot* StatusSlot = Root->AddChildToVerticalBox(StatusText))
	{
		StatusSlot->SetHorizontalAlignment(HAlign_Fill);
	}
}

UEditableTextBox* URadnodzDemoFromAI::AddLabeledTextBox(
	UVerticalBox* Parent,
	const FName& WidgetName,
	const FString& Label,
	const FString& Hint,
	const FString& InitialText)
{
	UHorizontalBox* Row = WidgetTree->ConstructWidget<UHorizontalBox>(UHorizontalBox::StaticClass());

	// 左: ラベル。幅を固定して入力欄の左端をそろえる
	UTextBlock* LabelText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass());
	LabelText->SetText(FText::FromString(Label));
	LabelText->SetFont(BodyFont());

	USizeBox* LabelArea = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass());
	LabelArea->SetWidthOverride(LabelWidth);
	LabelArea->AddChild(LabelText);

	if (USizeBoxSlot* LabelTextSlot = Cast<USizeBoxSlot>(LabelText->Slot))
	{
		LabelTextSlot->SetVerticalAlignment(VAlign_Center);
	}

	if (UHorizontalBoxSlot* LabelSlot = Row->AddChildToHorizontalBox(LabelArea))
	{
		LabelSlot->SetVerticalAlignment(VAlign_Center);
	}

	// 右: 入力欄。残り幅いっぱいに広げる
	UEditableTextBox* TextBox = WidgetTree->ConstructWidget<UEditableTextBox>(UEditableTextBox::StaticClass(), WidgetName);
	ApplyBlackText(TextBox);
	TextBox->SetHintText(FText::FromString(Hint));
	if (!InitialText.IsEmpty())
	{
		TextBox->SetText(FText::FromString(InitialText));
	}

	if (UHorizontalBoxSlot* TextBoxSlot = Row->AddChildToHorizontalBox(TextBox))
	{
		TextBoxSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
		TextBoxSlot->SetVerticalAlignment(VAlign_Center);
	}

	if (UVerticalBoxSlot* RowSlot = Parent->AddChildToVerticalBox(Row))
	{
		RowSlot->SetPadding(FMargin(0.f, RowSpacing));
		RowSlot->SetHorizontalAlignment(HAlign_Fill);
	}

	return TextBox;
}

// ============================================================
//  ライフサイクル
// ============================================================

void URadnodzDemoFromAI::NativeConstruct()
{
	Super::NativeConstruct();

	SetStatus(TEXT("IP / ポート / 新しい名前を入力して実行してください"));
}

void URadnodzDemoFromAI::NativeDestruct()
{
	// 画面を閉じるときは必ず切っておく
	if (Client && Client->IsConnected())
	{
		Client->Disconnect();
	}

	Super::NativeDestruct();
}

// ============================================================
//  REAPER とのやりとり
// ============================================================

void URadnodzDemoFromAI::SetStatus(const FString& Message)
{
	if (StatusText)
	{
		StatusText->SetText(FText::FromString(Message));
	}

	UE_LOG(LogTemp, Log, TEXT("[RadnodzDemoFromAI] %s"), *Message);
}

bool URadnodzDemoFromAI::EnsureConnected()
{
	const FString Ip = IpTextBox->GetText().ToString().TrimStartAndEnd();
	const FString PortString = PortTextBox->GetText().ToString().TrimStartAndEnd();

	if (Ip.IsEmpty())
	{
		SetStatus(TEXT("IP を入力してください"));
		return false;
	}

	if (!PortString.IsNumeric())
	{
		SetStatus(TEXT("ポートは数字で入力してください"));
		return false;
	}

	const int Port = FCString::Atoi(*PortString);

	// クライアントは初回だけ生成し、以降は使い回す
	if (!Client)
	{
		Client = URadnodzClient::CreateRadnodzClient();
	}

	// 同じ相手につながったままなら、そのまま使う
	if (Client->IsConnected())
	{
		if (ConnectedIp == Ip && ConnectedPort == Port)
		{
			return true;
		}

		// 入力が変わったので張り直す
		Client->Disconnect();
	}

	SetStatus(FString::Printf(TEXT("接続中... %s:%d"), *Ip, Port));

	Client->Connect(Ip, Port);

	// 成否は戻り値ではなく GetError() で判定する
	const FRigdocksError Error = Client->GetError();
	if (!IsSuccess(Error) || !Client->IsConnected())
	{
		ConnectedIp.Reset();
		ConnectedPort = 0;
		SetStatus(FString::Printf(TEXT("接続失敗 %s"), *FormatError(Error)));
		return false;
	}

	ConnectedIp = Ip;
	ConnectedPort = Port;
	return true;
}

void URadnodzDemoFromAI::OnRunClicked()
{
	// ---- 1. 新しい名前を先に確かめる(接続前に弾けるものは弾く)----
	const FString NewName = NameTextBox->GetText().ToString().TrimStartAndEnd();
	if (NewName.IsEmpty())
	{
		SetStatus(TEXT("新しい名前を入力してください"));
		return;
	}

	// ---- 2. 接続する ----
	if (!EnsureConnected())
	{
		return;
	}

	// ---- 3. 選択中の 1 つ目のメディアアイテムをリネームする ----
	// proj に nullptr を渡すと、プラグイン側が今開いているプロジェクトを使う
	URigdocksBronze_MediaItem::AZ_SetSelectedMediaName(Client, nullptr, FirstSelectedItemID, NewName);

	// ---- 4. 成否を確認する ----
	const FRigdocksError Error = Client->GetError();
	if (!IsSuccess(Error))
	{
		SetStatus(FString::Printf(TEXT("リネーム失敗 %s"), *FormatError(Error)));
		return;
	}

	SetStatus(FString::Printf(TEXT("リネーム成功: %s"), *NewName));
}

Introduction

Reaperとの接続・APIの実行

01_BRONZE_ss_edited.png
01_BRONZE_ss_edited.png
01_SILVER_edited_edited.png
03_GOLD_edited_edited.png

RADNODZを使用したUE5からReaperへの接続・APIの実行

Refresh
bottom of page