■プログラムの概要
・前回投稿(*)の改良版です。透過処理により、1つのアイコン画像だけで、あたかも
2つのアイコンを切り替えているように見せる方法を紹介します。
背景画像にデフォルトの画像を描画しておき、その真上にアイコン画像(下記コードの
setAlphaを検索下さい)を配置し、タップに応じて透過制御すればOKです。
(*)https://qiita.com/voibow/items/bf8c66efbcfe803d5053
・用途:電子楽器。
※YouTube動画リンク先:https://www.youtube.com/watch?v=Y2-1u_X-9jc
■Android側のプログラムのソースコード
(補足)各種アイコン(piston_icon.png等)や背景画像(bg.jpg)は、事前にpptで作成。
作成方法:https://qiita.com/voibow/items/6f273a903c648beff77d
<?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.Piston_20220619.MainActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="image1"
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="185dp"
android:layout_marginLeft="110dp"
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="0dp"
android:layout_marginLeft="25dp"
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_flt"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginLeft="320dp"
android:textSize="25sp"
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_atk"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="90dp"
android:layout_marginLeft="320dp"
android:textSize="25sp"
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_oct"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="180dp"
android:layout_marginLeft="320dp"
android:textSize="25sp"
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_pst1"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="280dp"
android:layout_marginLeft="100dp"
android:textSize="27sp"
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_pst2"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="315dp"
android:layout_marginLeft="100dp"
android:textSize="27sp"
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_x0"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="243dp"
android:layout_marginLeft="125dp"
android:textSize="20sp"
android:textColor="#ffffff"
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_y0"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="243dp"
android:layout_marginLeft="225dp"
android:textSize="20sp"
android:textColor="#ffffff"
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_x1"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="62dp"
android:layout_marginLeft="420dp"
android:textSize="20sp"
android:textColor="#ffffff"
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_y1"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="62dp"
android:layout_marginLeft="520dp"
android:textSize="20sp"
android:textColor="#ffffff"
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_x2"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="155dp"
android:layout_marginLeft="420dp"
android:textSize="20sp"
android:textColor="#ffffff"
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_y2"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="155dp"
android:layout_marginLeft="520dp"
android:textSize="20sp"
android:textColor="#ffffff"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.Piston_20220619.CustomImageView
android:id="@+id/image_view_glitter"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="0dp"
android:layout_marginTop="140dp"
android:src="@drawable/glitter_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.Piston_20220619.CustomImageView
android:id="@+id/image_view_flutter"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="350dp"
android:layout_marginTop="0dp"
android:src="@drawable/flutter_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.Piston_20220619.CustomImageView
android:id="@+id/image_view_attack"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="350dp"
android:layout_marginTop="95dp"
android:src="@drawable/attack_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.Piston_20220619.CustomImageView
android:id="@+id/image_view_tona_left"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginLeft="-20dp"
android:layout_marginTop="277dp"
android:src="@drawable/tona_left_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.Piston_20220619.CustomImageView
android:id="@+id/image_view_tona_right"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginLeft="38dp"
android:layout_marginTop="277dp"
android:src="@drawable/tona_right_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.Piston_20220619.CustomImageView
android:id="@+id/image_view_piston1"
android:layout_width="92dp"
android:layout_height="100dp"
android:layout_marginLeft="251dp"
android:layout_marginTop="275dp"
android:src="@drawable/piston_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.Piston_20220619.CustomImageView
android:id="@+id/image_view_piston2"
android:layout_width="92dp"
android:layout_height="100dp"
android:layout_marginLeft="373dp"
android:layout_marginTop="275dp"
android:src="@drawable/piston_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.Piston_20220619.CustomImageView
android:id="@+id/image_view_piston3"
android:layout_width="92dp"
android:layout_height="100dp"
android:layout_marginLeft="495dp"
android:layout_marginTop="275dp"
android:src="@drawable/piston_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</RelativeLayout>
//参照サイト:https://www.wabiapp.com/WabiSampleSource/windows/udp_server.html
package com.example.Piston_20220619;
import androidx.appcompat.app.AppCompatActivity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
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 {
// 各種変数宣言
CustomImageView[] cImageView = new CustomImageView[5];
TextView textView_apt, textView_cav, textView_flt;
TextView textView_atk, textView_oct, textView_pst1, textView_pst2;
TextView textView_x0,textView_y0,textView_x1,textView_y1;//デバッグ用途
TextView textView_x2,textView_y2;//デバッグ用途
// タップ座標、エリア
int[] newDx = new int[10]; int[] newDy = new int[10];
int[] preDx = new int[10]; int[] preDy = new int[10];
int[] max_x = new int[10]; int[] max_y = new int[10];
int[] min_x = new int[10]; int[] min_y = new int[10];
int tap_area; boolean oct_button;
boolean tona_left_state = false; boolean tona_right_state = false;
int piston_mode = 0, piston_state = 0;
// パソコンに送信する値
int posi_apt, posi_cav, posi_flt, posi_atk, posi_oct, stat_pst=0;
String str_apt, str_cav, str_flt, str_atk, str_oct, str_pst1, str_pst2;
@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);
textView_flt = findViewById(R.id.text_view_flt);
textView_atk = findViewById(R.id.text_view_atk);
textView_oct = findViewById(R.id.text_view_oct);
textView_pst1 = findViewById(R.id.text_view_pst1);
textView_pst2 = findViewById(R.id.text_view_pst2);
cImageView[0] = this.findViewById(R.id.image_view_glitter);
cImageView[1] = this.findViewById(R.id.image_view_flutter);
cImageView[2] = this.findViewById(R.id.image_view_attack);
cImageView[3] = this.findViewById(R.id.image_view_octave0);
cImageView[3].setAlpha(0.0f);//0:透明
cImageView[4] = this.findViewById(R.id.image_view_tona_left);
cImageView[5] = this.findViewById(R.id.image_view_tona_right);
cImageView[4].setAlpha(0.0f);//0:透明
cImageView[5].setAlpha(0.0f);//0:透明
cImageView[6] = this.findViewById(R.id.image_view_piston1);
cImageView[7] = this.findViewById(R.id.image_view_piston2);
cImageView[8] = this.findViewById(R.id.image_view_piston3);
cImageView[6].setAlpha(0.0f);//0:透明
cImageView[7].setAlpha(0.0f);//0:透明
cImageView[8].setAlpha(0.0f);//0:透明
cImageView[0].setOnTouchListener(this);
cImageView[1].setOnTouchListener(this);
cImageView[2].setOnTouchListener(this);
cImageView[3].setOnTouchListener(this);
cImageView[4].setOnTouchListener(this);
cImageView[5].setOnTouchListener(this);
cImageView[6].setOnTouchListener(this);
cImageView[7].setOnTouchListener(this);
cImageView[8].setOnTouchListener(this);
// デバッグ表示用の変数定義
textView_x0 = findViewById(R.id.text_view_x0);
textView_y0 = findViewById(R.id.text_view_y0);
textView_x1 = findViewById(R.id.text_view_x1);
textView_y1 = findViewById(R.id.text_view_y1);
textView_x2 = findViewById(R.id.text_view_x2);
textView_y2 = findViewById(R.id.text_view_y2);
// 初期設定(画面の各エリア範囲を規定)
max_x[0]=720; min_x[0]=200; max_y[0]=700; min_y[0]=200;//glitterエリア
max_x[1]=1600; min_x[1]=1300; max_y[1]=200; min_y[1]=100;//flutterエリア
max_x[2]=1600; min_x[2]=1300; max_y[2]=550; min_y[2]=350;//attackエリア
max_x[3]=1600; min_x[3]=1300; max_y[3]=750; min_y[3]=650;//octaveエリア
max_x[4]=100; min_x[4]=0; max_y[4]=1200; min_y[4]=850;//tonality_leftエリア
max_x[5]=650; min_x[5]=500; max_y[5]=1200; min_y[5]=850;//tonality_rightエリア
max_x[6]=1100; min_x[6]=950; max_y[6]=1200; min_y[6]=850;//piston1エリア
max_x[7]=1400; min_x[7]=1200; max_y[7]=1200; min_y[7]=850;//piston2エリア
max_x[8]=1700; min_x[8]=1500; max_y[8]=1200; min_y[8]=850;//piston3エリア
tap_area = -1;//どのエリアでもない
oct_button = false;// 通常ピッチ(octシフトしていない)
// 初期表示
String str_apt = "apt=" + 100;
textView_apt.setText(str_apt);
String str_cav = "cav=" + 100;
textView_cav.setText(str_cav);
String str_flt = "flt=" + 0;
textView_flt.setText(str_flt);
String str_atk = "atk=" + 0;
textView_atk.setText(str_atk);
String str_oct = "oct=" + 0;
textView_oct.setText(str_oct);
str_pst1 = "オートマ";
textView_pst1.setTextColor(Color.rgb(188, 226, 232));//水色
textView_pst1.setText(str_pst1);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//タップ座標@アイコンの取得、各アイコンのエリア規定
newDx[0] = Math.min(Math.max((int)event.getRawX(),min_x[0]),max_x[0]);
newDy[0] = Math.min(Math.max((int)event.getRawY(),min_y[0]),max_y[0]);
newDx[1] = Math.min(Math.max((int)event.getRawX(),min_x[1]),max_x[1]);
newDy[1] = Math.min(Math.max((int)event.getRawY(),min_y[1]),max_y[1]);
newDx[2] = Math.min(Math.max((int)event.getRawX(),min_x[2]),max_x[2]);
newDy[2] = Math.min(Math.max((int)event.getRawY(),min_y[2]),max_y[2]);
newDx[3] = Math.min(Math.max((int)event.getRawX(),min_x[3]),max_x[3]);
newDy[3] = Math.min(Math.max((int)event.getRawY(),min_y[3]),max_y[3]);
newDx[4] = Math.min(Math.max((int)event.getRawX(),min_x[4]),max_x[4]);
newDy[4] = Math.min(Math.max((int)event.getRawY(),min_y[4]),max_y[4]);
newDx[5] = Math.min(Math.max((int)event.getRawX(),min_x[5]),max_x[5]);
newDy[5] = Math.min(Math.max((int)event.getRawY(),min_y[5]),max_y[5]);
newDx[6] = Math.min(Math.max((int)event.getRawX(),min_x[6]),max_x[6]);
newDy[6] = Math.min(Math.max((int)event.getRawY(),min_y[6]),max_y[6]);
newDx[7] = Math.min(Math.max((int)event.getRawX(),min_x[7]),max_x[7]);
newDy[7] = Math.min(Math.max((int)event.getRawY(),min_y[7]),max_y[7]);
newDx[8] = Math.min(Math.max((int)event.getRawX(),min_x[8]),max_x[8]);
newDy[8] = Math.min(Math.max((int)event.getRawY(),min_y[8]),max_y[8]);
if(newDx[0]<max_x[0] && newDx[0]>=min_x[0] && newDy[0]<max_y[0] && newDy[0]>=min_y[0]) {
tap_area = 0;//glitterエリア
// 【デバッグ表示】newDxなど
String str_x0 = "x0=" + newDx[0];
textView_x0.setText(str_x0);
String str_y0 = "y0=" + newDy[0];
textView_y0.setText(str_y0);
}
else if(newDx[1]<max_x[1] && newDx[1]>=min_x[1] && newDy[1]<max_y[1] && newDy[1]>=min_y[1]) {
tap_area = 1;//flutterエリア
// 【デバッグ表示】newDxなど
String str_x1 = "x1=" + newDx[1];
textView_x1.setText(str_x1);
String str_y1 = "y1=" + newDy[1];
textView_y1.setText(str_y1);
}
else if(newDx[2]<max_x[2] && newDx[2]>=min_x[2] && newDy[2]<max_y[2] && newDy[2]>=min_y[2]) {
tap_area = 2;//attackエリア
// 【デバッグ表示】newDxなど
String str_x2 = "x2=" + newDx[2];
textView_x2.setText(str_x2);
String str_y2 = "y2=" + newDy[2];
textView_y2.setText(str_y2);
}
else if(newDx[3]<max_x[3] && newDx[3]>=min_x[3] && newDy[3]<max_y[3] && newDy[3]>=min_y[3]) {
tap_area = 3;//octaveエリア
}
else if(newDx[4]<max_x[4] && newDx[4]>=min_x[4] && newDy[4]<max_y[4] && newDy[4]>=min_y[4]) {
tap_area = 4;//tonality_leftエリア
}
else if(newDx[5]<max_x[5] && newDx[5]>=min_x[5] && newDy[5]<max_y[5] && newDy[5]>=min_y[5]) {
tap_area = 5;//tonality_rightエリア
}
else if(newDx[6]<max_x[6] && newDx[6]>=min_x[6] && newDy[6]<max_y[6] && newDy[6]>=min_y[6]) {
tap_area = 6;//piston1エリア
}
else if(newDx[7]<max_x[7] && newDx[7]>=min_x[7] && newDy[7]<max_y[7] && newDy[7]>=min_y[7]) {
tap_area = 7;//piston2エリア
}
else if(newDx[8]<max_x[8] && newDx[8]>=min_x[8] && newDy[8]<max_y[8] && newDy[8]>=min_y[8]) {
tap_area = 8;//piston3エリア
}
else {
tap_area = -1;//どのエリアでもない
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:// オクターブ切替のみタッチ制御にする
if(tap_area == 3) {
if(oct_button) {
cImageView[3].setImageResource(R.drawable.oct_down_icon);
cImageView[3].setAlpha(0.0f);//0:透明 ~ 1:非透明
oct_button = !oct_button;
posi_oct = 0;
}
else {
cImageView[3].setImageResource(R.drawable.oct_down_icon);
cImageView[3].setAlpha(1.0f);//0:透明 ~ 1:非透明
oct_button = !oct_button;
posi_oct = 1;
}
// パラメータ値の画面表示
String str_oct = "oct=" + posi_oct;
textView_oct.setText(str_oct);
}
else if(tap_area == 4) {//tonality_leftをON
tona_left_state = true;
}
else if(tap_area == 5) {//tonality_rightをON
tona_right_state = true;
}
else if(tap_area == 6 && piston_mode >= 0x80) {//piston1をON@マニュアルモード
piston_state |= 4;
cImageView[6].setAlpha(1.0f);//0:透明 ~ 1:非透明
}
else if(tap_area == 7 && piston_mode >= 0x80) {//piston2をON@マニュアルモード
piston_state |= 2;
cImageView[7].setAlpha(1.0f);//0:透明 ~ 1:非透明
}
else if(tap_area == 8 && piston_mode >= 0x80) {//piston3をON@マニュアルモード
piston_state |= 1;
cImageView[8].setAlpha(1.0f);//0:透明 ~ 1:非透明
}
// piston_mode判定 ※ACTION_DOWNの瞬間に判定する(ACTION_UPでは判定しない)。
if(tap_area == 4 || tap_area == 5) {
// マニュアルモードに設定
if ((tona_left_state && !tona_right_state) || (!tona_left_state && tona_right_state)) {
cImageView[4].setAlpha(1.0f);//0:透明 ~ 1:非透明
cImageView[5].setAlpha(1.0f);//0:透明 ~ 1:非透明
if(piston_mode == 0) { piston_mode = 0x80+6; } //A#3(B♭)
else {
if(tap_area == 4) { piston_mode = Math.max(piston_mode-1,0x80); }
else { piston_mode = Math.min(piston_mode+1,0x80+11); }
}
switch(piston_mode & 0x7F) {//MSb:マニュアルフラグ
case 0: str_pst2 = "key=E3"; break;
case 1: str_pst2 = "key=F3"; break;
case 2: str_pst2 = "key=F#3"; break;
case 3: str_pst2 = "key=G3"; break;
case 4: str_pst2 = "key=G#3"; break;
case 5: str_pst2 = "key=A3"; break;
case 6: str_pst2 = "key=A#3(B♭)"; break;
case 7: str_pst2 = "key=B3"; break;
case 8: str_pst2 = "key=C4"; break;
case 9: str_pst2 = "key=C#4"; break;
case 10: str_pst2 = "key=D4"; break;
case 11: str_pst2 = "key=D#4"; break;
default: str_pst2 = "設定失敗"; break;
}
str_pst1 = "マニュアル";
textView_pst1.setTextColor(Color.rgb(255, 255, 0));//黄色
textView_pst1.setText(str_pst1);
textView_pst2.setTextColor(Color.rgb(255, 255, 0));//黄色
textView_pst2.setText(str_pst2);
}
// オートマモードに設定 @tona_leftとtona_right両方ON
else {
piston_mode = 0;
cImageView[4].setAlpha(0.0f);//0:透明 ~ 1:非透明
cImageView[5].setAlpha(0.0f);//0:透明 ~ 1:非透明
str_pst1 = "オートマ";
textView_pst1.setTextColor(Color.rgb(188, 226, 232));//水色
textView_pst1.setText(str_pst1);
str_pst2 = "";
textView_pst2.setText(str_pst2);
}
}
// Log.d("★","piston_mode="+Integer.toBinaryString(piston_mode));
break;
case MotionEvent.ACTION_UP:
if(tap_area == 4) {//tonality_leftをOFF
tona_left_state = false;
}
if(tap_area == 5) {//tonality_rightをOFF
tona_right_state = false;
}
if(tap_area == 6) {//piston1をOFF
piston_state &= 3;
cImageView[6].setAlpha(0.0f);//0:透明 ~ 1:非透明
}
if(tap_area == 7) {//piston2をOFF
piston_state &= 5;
cImageView[7].setAlpha(0.0f);//0:透明 ~ 1:非透明
}
if(tap_area == 8) {//piston3をOFF
piston_state &= 6;
cImageView[8].setAlpha(0.0f);//0:透明 ~ 1:非透明
}
break;
case MotionEvent.ACTION_MOVE:
int dx,dy,imgW,imgH;
// glitterエリアでの処理
if(tap_area == 0) {
// 画像タップ位置を決定
dx = cImageView[0].getLeft() + (newDx[0] - preDx[0]);
dy = cImageView[0].getTop() + (newDy[0] - preDy[0]);
imgW = dx + cImageView[0].getWidth();
imgH = dy + cImageView[0].getHeight();
cImageView[0].layout(dx, dy, imgW, imgH);
// 画像タップ位置に基づき、パラメータ値を生成(線形補間)
float nume_apt=(float)(newDx[0]-min_x[0]);//分子
float deno_apt=(float)(max_x[0]-min_x[0]);//分母
float tilt_apt=-50;//傾き
float inter_apt=100;//切片
float nume_cav=(float)(newDy[0]-min_y[0]);//分子
float deno_cav=(float)(max_y[0]-min_y[0]);//分母
float tilt_cav=50;//傾き
float inter_cav=50;//切片
posi_apt = (int)Math.round((tilt_apt*nume_apt/deno_apt+inter_apt));//100~50を表示
posi_cav = (int)Math.round((tilt_cav*nume_cav/deno_cav+inter_cav));//50-100を表示
// パラメータ値の画面表示
String str_apt = "apt=" + posi_apt;
textView_apt.setText(str_apt);
String str_cav = "cav=" + posi_cav;
textView_cav.setText(str_cav);
// Log.d("●onTouch●", "ACTION_MOVE: dx=" + dx + ", dy=" + dy);
}
// フラッターエリアの処理
else if(tap_area == 1) {
// 画像タップ位置を決定
dx = cImageView[1].getLeft() + (newDx[1] - preDx[1]);
dy = cImageView[1].getTop() + (newDy[1] - preDy[1]);
imgW = dx + cImageView[1].getWidth();
imgH = dy + cImageView[1].getHeight();
cImageView[1].layout(dx, dy, imgW, imgH);
// 画像タップ位置に基づき、パラメータ値を生成(線形補間)
float nume_flt=(float)(newDx[1]-min_x[1]);//分子
float deno_flt=(float)(max_x[1]-min_x[1]);//分母
float tilt_flt=10;//傾き
float inter_flt=0;//切片
posi_flt = (int)Math.round((tilt_flt*nume_flt/deno_flt+inter_flt));//0~10を表示
// パラメータ値の画面表示
String str_flt = "flt=" + posi_flt;
textView_flt.setText(str_flt);
}
// アタックエリアの処理
else if(tap_area == 2) {
// 画像タップ位置を決定
dx = cImageView[2].getLeft() + (newDx[2] - preDx[2]);
dy = cImageView[2].getTop() + (newDy[2] - preDy[2]);
imgW = dx + cImageView[2].getWidth();
imgH = dy + cImageView[2].getHeight();
cImageView[2].layout(dx, dy, imgW, imgH);
// 画像タップ位置に基づき、パラメータ値を生成(線形補間)
float nume_atk=(float)(newDx[2]-min_x[2]);//分子
float deno_atk=(float)(max_x[2]-min_x[2]);//分母
float tilt_atk=10;//傾き
float inter_atk=0;//切片
posi_atk = (int)Math.round((tilt_atk*nume_atk/deno_atk+inter_atk));//0~10を表示
// パラメータ値の画面表示
String str_atk = "atk=" + posi_atk;
textView_atk.setText(str_atk);
}
break;
default:
break;
}// switch (event.getAction())
// stat_pstの生成
stat_pst = piston_mode | piston_state<<4;
// タッチした位置を古い位置とする
// アイコン座標の移動処理を行うエリアのみ下記処理を行う。
if(tap_area == 0) {
preDx[0] = newDx[0];
preDy[0] = newDy[0];
}
else if(tap_area == 1) {
preDx[1] = newDx[1];
preDy[1] = newDy[1];
}
else if(tap_area == 2) {
preDx[2] = newDx[2];
preDy[2] = newDy[2];
}
// UDP送信スレッド
new Thread(new Runnable() {
public void run() {
try {
// ソケットオープン
DatagramSocket sendUdpSocket = new DatagramSocket();
InetAddress IPAddress = InetAddress.getByName(“hogehoge");
int port = hogehoge;//ポート番号
// 送信フォーム:apt,cav,flt
String[] s = new String[5];
s[0] = String.valueOf(posi_apt);
s[1] = String.valueOf(posi_cav);
s[2] = String.valueOf(posi_flt);
s[3] = String.valueOf(posi_atk);
s[4] = String.valueOf(posi_oct);
s[5] = String.valueOf(stat_pst);
String str = s[0] + "," + s[1]+ "," + s[2] + "," + s[3] + "," + s[4] + "," + s[5];
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関連
//UDP受信するパラメータ
static float udp_apt, udp_cav, udp_flt, udp_atk, udp_oct;
static int udp_pst;
//▲その他、必要に応じて
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[30];//Android側からの単位送信長は30キャラ未満
//受信
::recvfrom(
oSocket
, (char*)szData
, sizeof(szData) - 1
, 0
, (struct sockaddr*)&oFromAddr
, &sockaddr_in_size
);
//カンマ区切りの受信データを変数にストア
//受信データは、[数値(apt),数値(cav),数値(flt)・・・]の形式で受信
sscanf(szData, "%f ,%f ,%f ,%f ,%f, %d", &udp_apt, &udp_cav, &udp_flt, &udp_atk, &udp_oct, &udp_pst);
//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);//音色制御処理のコール
// フラッター制御
float flt;
flt = udp_flt*0.1;//udp_flt:0~10 → fltゲイン:0~1
synth.midi_flutter_event(flt);
// アタック制御
float atk;
atk = udp_atk*0.8;//udp_atk:0~10 → 0~8
synth.midi_attack_event(atk);
// オクターブ制御
float oct;
oct = udp_oct;//udp_ock:0/1
synth.midi_octave_event(oct);
// ピストン制御
int pst;
pst = udp_pst;
synth.midi_piston_event(udp_pst);
// OutputDebugString(szData);//★受信データの表示
}
break;
}
}
break;
//▲その他処理は必要に応じてcase文で追加(ダイアログボックスの制御など)
default:
break;
}
return FALSE;
}
■動作結果
【Android端末側】
・下記スクリーンショット①②の、下辺部分を見比べてください。①は起動直後の表示態様です。水色の◁▷や、リリース状態のピストンアイコンは、事前に背景画像(bg.jpg)に描画されたものであり、(視認できませんが)それらの手前側に②の下辺部分に示した透明/非透明制御できるアイコンが配置されてます(起動時は透明)。◁▷部分をタッチすることで、前記透明アイコンが非透明化され、黄色の◁▷が出現します。同様にピストン部分をタッチすることで、押下状態のピストンが出現します。すなわち、各アイコン毎に2種類のアイコンを準備/切替制御しなくとも、「背景画像」と「アイコンの透過処理」のコンビネーションで、あたかも2つのアイコンが切り替わったかのように表示することが可能となるわけです。
●スクリーンショット①
●スクリーンショット②
・(余談)下記リンク先のYouTube動画のように、マニュアルモード(調性:B♭)にて、ピストンを操作しながら演奏ができます。マニュアルモードは運指を覚えないといけないので面倒ですが、音階移動時の不連続感(「バジングピッチ」と「管長制御」の時間的ずれ)を表現できます。一方、当該ずれをなくしスムーズに(歌うように)演奏したい場合はオートマモードの方が向いていると思います。
※YouTube動画リンク先::https://www.youtube.com/watch?v=Y2-1u_X-9jc
【PC側】
・注意:Android(スマホ)とPCのWiFiをONにして下さい。
・送信確認ツール(Wireshark)を起動し、上記通りAndroidを
タップするとUDP送信ログが表示されます。
・winapiコードの「★受信データの表示」の行のコメントアウトを
外し、各種アイコンをタップして動かすと、
VisualStudioの出力ウインドウに、下記のような表示が出ればOKです。
表示例:90,95,3,5,0,134フフフフフフフフフ88,90,0,1,1,168フフフフフフフフフ
・あとは、取得した数値(90等)を使って、各自が作成された音処理系
に渡せば(音色はともかく)同じような動作が実現できるはずです。
・通信不良の場合、PCのIPアドレスやポート番号(hogehogeと記載した
部分)の設定ミス、WiFi未接続などが原因と思われます。
なおIPアドレスは、固定アドレス化しておいた方が便利です。
■今後の課題
winapi側(含む音処理)すべてをAndroid側にJNIベースで集約していく予定です。
これにより、Android端末とピンマイクだけで電子楽器を実現できるはずですが、
レイテンシが許容範囲に収まるかどうかが最大の懸念事項です。