■プログラムの概要
・Android側:画面アイコンをタップ移動した際の座標をUDP送信。
・PC側:winapi(言語:C++)ベースで作成したUDP受信処理。
・開発環境:AndroidStudioBumblebee2021.1.1 VisualSutudio2013
・送信確認ツール:Wireshark ver3.6.3
・用途:電子楽器。※YouTube動画リンク先:https://youtu.be/9AdsV2z7p_E
・備考:UDP受信後の処理(音生成/音色制御)は割愛してます。
■Android側のプログラムのソースコード
(補足)touch_iconはトランペットアイコン、bgは背景画像。いずれもpptで作成。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fed"
tools:context="com.example.Glitter_20220504.MainActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/img_description1"
android:scaleType="centerCrop"
android:src="@drawable/bg" />
<TextView
android:id="@+id/text_view_apt"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="260dp"
android:layout_marginLeft="350dp"
android:textSize="50sp"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_view_cav"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="60dp"
android:textSize="50sp"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.Glitter_20220504.CustomImageView
android:id="@+id/image_view"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="100dp"
android:layout_marginTop="100dp"
android:contentDescription="@string/description"
android:src="@drawable/touch_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_view" />
</RelativeLayout>
//参照サイト:https://www.wabiapp.com/WabiSampleSource/windows/udp_server.html
package com.example.Glitter_20220504;
import androidx.appcompat.app.AppCompatActivity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class MainActivity extends AppCompatActivity
implements View.OnTouchListener {
private CustomImageView cImageView;
private int preDx, preDy;
private TextView textView_apt;
private TextView textView_cav;
//パソコンに送信する値
int posi_apt;
int posi_cav;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//横画面に固定する
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
textView_apt = findViewById(R.id.text_view_apt);
textView_cav = findViewById(R.id.text_view_cav);
cImageView = this.findViewById(R.id.image_view);
cImageView.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
// x,y 位置取得
int newDx = (int)event.getRawX();
int newDy = (int)event.getRawY();
switch (event.getAction()) {
// タッチダウンでdragされた
case MotionEvent.ACTION_MOVE:
// ACTION_MOVEでの位置
// performCheckを入れろと警告が出るので
v.performClick();
int dx = cImageView.getLeft() + (newDx - preDx);
int dy = cImageView.getTop() + (newDy - preDy);
int max_x=1500; int max_y=800;
if(dx >= max_x) { dx = max_x; } if(dx <= 0) { dx = 0; }
if(dy >= max_y) { dy = max_y; } if(dy <= 0) { dy = 0; }
int imgW = dx + cImageView.getWidth();
int imgH = dy + cImageView.getHeight();
//座標をudp送信処理に渡す
posi_apt = (int)(((float)dx/(float)max_x)*(-50)+100);//100~50を表示
posi_cav = (int)((((float)max_y-(float)dy)/(float)max_y)*(-50)+100);//同上
// 画像の位置を設定する
cImageView.layout(dx, dy, imgW, imgH);
String str_apt = "apt="+posi_apt;
textView_apt.setText(str_apt);
String str_cav = "cav="+posi_cav;
textView_cav.setText(str_cav);
break;
case MotionEvent.ACTION_DOWN:
// nothing to do
break;
case MotionEvent.ACTION_UP:
// nothing to do
break;
default:
break;
}
// タッチした位置を古い位置とする
preDx = newDx;
preDy = newDy;
//ここから、UDP送信スレッド
new Thread(new Runnable() {
public void run() {
try {
// ソケットオープン
DatagramSocket sendUdpSocket = new DatagramSocket();
InetAddress IPAddress = InetAddress.getByName(“hogehoge");
int port = hogehoge;//ポート番号
String s_x = String.valueOf(posi_apt);
String s_y = String.valueOf(posi_cav);
String str = s_x + "," + s_y
byte[] sendData = str.getBytes("UTF-8");
// 送信先と送信データを設定
DatagramPacket sendPacket = new DatagramPacket(
sendData, sendData.length, IPAddress, port);
// テキストデータを送信
sendUdpSocket.send(sendPacket);
// ソケットを閉じる
sendUdpSocket.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}).start();
// 以上、UDP送信
return true;
}
}
■PC側のプログラムのソースコード
//参照サイト①:https://www.wabiapp.com/WabiSampleSource/windows/udp_server.html
//参照サイト②:https://www.kana-soft.com/tech/sample_0004.htm
//注意:▲部分はUDP処理と無関係なので、皆様の仕様に合わせて記述下さい。
//一般的なヘッダー
#include <windows.h>
#include <windowsX.h>
#include <stdio.h>
//▲その他、必要に応じて
//UDP関連
#pragma comment( lib, "ws2_32.lib" )//ライブラリ
#define WM_WINSOCKEVENT ( WM_USER + 100 )// WinSockからのイベント
//関数プロトタイプ、変数
static LRESULT CALLBACK SoundProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam );
static void InitDialog( HWND hDlg );//▲
static HWND AppWnd;//▲アプリケーションのウインドウハンドル
static SOCKET oSocket;//UDP関連
static float udp_apt, udp_cav;//UDP受信するパラメータ
//▲その他、必要に応じて
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPreInst, LPSTR CmdLine, int show )
{
//▲
AppWnd = NULL;
DialogBox( hInst, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, (DLGPROC)SoundProc );
return 0;
}
static LRESULT CALLBACK SoundProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg ){
case WM_INITDIALOG://初期化全般
InitDialog(hDlg);//▲ダイアログボックスの初期化
//UDP初期化
WSAData wsaData;
WSAStartup(MAKEWORD(2, 0), &wsaData); //(2, 0)はwinsockのバージョン
//ソケットの生成
oSocket = socket(AF_INET, SOCK_DGRAM, 0); //AF_INETはIPv4、SOCK_DGRAMはUDP通信
// ソケットを非同期にする
WSAAsyncSelect(oSocket, hDlg, WM_WINSOCKEVENT, FD_READ | FD_CLOSE);
// アドレス等格納
struct sockaddr_in oSockAddr; //IPv4
oSockAddr.sin_family = AF_INET;
oSockAddr.sin_port = htons(hogehoge); //通信ポート番号設定
oSockAddr.sin_addr.S_un.S_addr = INADDR_ANY; // INADDR_ANY:全アドレス受信
// バインド
::bind(oSocket, (struct sockaddr*)&oSockAddr, sizeof(oSockAddr));
OutputDebugString("hogehoge");
break;
case WM_WINSOCKEVENT://UDPデータ受信
{
switch (WSAGETSELECTEVENT(lParam)) {
case FD_READ:
{
struct sockaddr_in oFromAddr;
int sockaddr_in_size = sizeof(struct sockaddr_in);
CHAR szData[10];//Android側からの単位送信長は10キャラ未満
//受信
::recvfrom(
oSocket
, (char*)szData
, sizeof(szData) - 1
, 0
, (struct sockaddr*)&oFromAddr
, &sockaddr_in_size
);
//カンマ区切りの受信データを変数にストア
//受信データは、[数値(apt),数値(cav)]の形式で受信
sscanf(szData, "%f ,%f", &udp_apt, &udp_cav);
//apertureによる華やかさの制御
float apt, cav;//いずれも50~100の範囲
apt = (-0.02*udp_apt + 2)*2;//0~2の範囲
synth.midi_aperture_event(apt);//音色制御処理のコール
//cavityによる華やかさの制御
cav = -0.02*udp_cav + 4;//2~3の範囲
synth.midi_cavity_event(cav);//音色制御処理のコール
// OutputDebugString(szData);//★受信データの表示
}
break;
}
}
break;
//▲その他処理は必要に応じてcase文で追加(ダイアログボックスの制御など)
default:
break;
}
return FALSE;
}
■動作結果
【Android側】
・下記スクリーンショットのような画面が表示されます。
・下記リンク先のYouTube動画のように、トランペットアイコンを
タップして動かした場合、画面のaptとcavの数値が変化すればOKです。
※YouTube動画リンク先:https://youtu.be/9AdsV2z7p_E
【PC側】
・注意:Android(スマホ)とPCのWiFiをONにして下さい。
・送信確認ツール(Wireshark)を起動し、上記通りAndroidを
タップするとUDP送信ログが表示されます。
・winapiコードの「★受信データの表示」の行のコメントアウトを
外し、トランペットアイコンをタップして動かすと、
VisualStudioの出力ウインドウに、下記のような表示が出ればOKです。
表示例:90,95フフフフフフフフフ88,90フフフフフフフフフ
・あとは、取得した数値(90等)を使って、各自が作成された音処理系
に渡せば(音色はともかく)同じような動作が実現できるはずです。
・通信不良の場合、IPaddrやポート番号(hogehogeと記載した部分)
の設定ミス、WiFi未接続などが原因と思われます。
■今後の課題
winapi側(含む音処理)すべてをAndroid側にJNIベースで集約していく予定です。