# HG changeset patch # User hh # Date 1574412016 -3600 # Node ID 16509f98f3012bc07aec1c2841e48f7ddc02274c -- diff -r 000000000000 -r 16509f98f301 android/michelson.studio/build.gradle --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/build.gradle Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,17 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0-alpha09' + } +} + +allprojects { + repositories { + jcenter() + google() + } +} diff -r 000000000000 -r 16509f98f301 android/michelson.studio/lib031-release/build.gradle --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/lib031-release/build.gradle Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,2 @@ +configurations.maybeCreate("default") +artifacts.add("default", file('lib031-release.aar')) \ No newline at end of file diff -r 000000000000 -r 16509f98f301 android/michelson.studio/local.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/local.properties Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,10 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file should *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=/home/L/_HH/obsah/TECHNO/Android/sdk \ No newline at end of file diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/build.gradle --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/build.gradle Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,45 @@ +apply plugin: 'com.android.application' + +android { + signingConfigs { + hh { + keyAlias 'androiddebugkey' + keyPassword 'android' + storeFile file('/home/hh/.android/debug.keystore') + storePassword 'android' + } + } + + compileSdkVersion 28 + + defaultConfig { + applicationId 'hh.michelson' + minSdkVersion 22 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + signingConfig signingConfigs.hh + } + buildTypes { + release { + signingConfig signingConfigs.hh + } + debug { + debuggable false + } + } + productFlavors { + } +} + +dependencies { + implementation project(':lib031-release') + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support:support-v4:28.0.0' + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:preference-v7:28.0.0' + implementation 'com.android.support:coordinatorlayout:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:design:28.0.0' +} diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/AndroidManifest.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/AndroidManifest.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/java/hh/michelson/A.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/java/hh/michelson/A.java Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,462 @@ +package hh.michelson; + +import android.content.Context; +import android.graphics.Point; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.view.WindowManager; +import android.widget.ListView; +import android.widget.SeekBar; + +import hh.lib.D; +import hh.lib.DA; +import hh.ui.FlatButton; + +public class A extends DA implements WavesFragment.OnFragmentInteractionListener, Handler.Callback { + /** + * ---------------------------------------------------------- + * main activity template with uninstall in menu + * ---------------------------------------------------------- + */ + + WavesFragment waveList; + boolean playing = C.playingDefault; + Wave wave = null; + double phase = 0; + boolean node = true; + + volatile double freq = C.freqDef.Default; + volatile double freqProgr = C.freqProgrDef.Default; + volatile double targetFreq, deltaFreq, dt; + final static int UPDFREQ = 0; + + double amplitude; + volatile double amplKoef = 0; + double maxY = 0; + boolean mute = false; + + double pulseSecs = C.pulseDef.Default; + final double pulseStep = (Math.log10(C.pulseDef.Max) - Math.log10(C.pulseDef.Min)) / C.maxBarProgress; /* in secs */ + int pulseCycles; /* pulse in cycles */ + boolean isInPulse = true; + + double pauseSecs = C.pauseDef.Default; + final double pauseStep = (Math.log10(C.pauseDef.Max) - Math.log10(C.pauseDef.Min)) / C.maxBarProgress; /* in secs */ + int pauseCycles; /* pause in cycles */ + volatile double pulsePausePhase = 0; + + double slope = C.slopeDef.Default; + final double slopeStep = (Math.log10(C.slopeDef.Max) - Math.log10(C.slopeDef.Min)) / C.maxBarProgress; + double slopeKoef = 1d; + final static int SETSLOPE = 1; + + Handle amplBar; + Handle pulseBar; + Handle pauseBar; + Handle slopeBar; + Handle freqBar; + Handle freqProgressBar; + Handle[] handles; + + AudioTrack audioTrack; + int buffsize; + class Samples { + short[] samples; + boolean ready; + + Samples(int n) { + samples = new short[n]; + } + } + Samples[] s; + Thread audioThread = null; + boolean audioIsActive = true; + + Handler h = new Handler(this); + + class AmplHandle extends Handle { + + AmplHandle(D d, double actual) { super(d, C.amplitudeDef, actual); } + + @Override + public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) { + super.onProgressChanged(bar, progress, fromUser); + getAmpl(); + } + + @Override + public boolean onLongClick(View v) { + final boolean r = super.onLongClick(v); + getAmpl(); + return r; + } + } + + class PulseHandle extends Handle { + + PulseHandle(D d, double actual) { super(d, C.pulseDef, actual); } + + @Override + double progress2value(int progress) { return progress == 0 ? 0d : Math.pow(10, progress * pulseStep - 1); } + + @Override + int value2progress(double value) { return value == 0 ? 0 : (int)((Math.log10(value) + 1) / pulseStep); } + } + + class PauseHandle extends Handle { + + PauseHandle(D d, double actual) { super(d, C.pauseDef, actual); } + + PauseHandle(D d, String label, double min, double max, double def, double actual, String key) { + super(d, label, min, max, def, actual, key); + } + + @Override + double progress2value(int progress) { return progress == 0 ? 0d : Math.pow(10, progress * pulseStep - 1); } + + @Override + int value2progress(double value) { return value == 0 ? 0 : (int)((Math.log10(value) + 1) / pauseStep); } + } + + class SlopeHandle extends Handle { + + SlopeHandle(D d, double actual) { super(d, C.slopeDef, actual); } + + SlopeHandle(D d, String label, double min, double max, double def, double actual, String key) { + super(d, label, min, max, def, actual, key); + } + + @Override + double progress2value(int progress) { return progress == 0 ? 0d : Math.pow(10, (progress + 1) * slopeStep); } + + @Override + int value2progress(double value) { return value == 0 ? 0 : (int)(Math.log10(value)/slopeStep - 1); } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.main); + + Point size = new Point(); + ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(size); + C.windowWidth = size.x; + + getValues(); + setMenu(); + findViewById(R.id.dispWave).setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + showListUI(); + } + }); + setControlHandles(); + findViewById(R.id.mute).setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + flipMute(); + } + }); + setMute(); + allocAudio(); + + if(savedInstanceState != null) playing = savedInstanceState.getBoolean(C.playingKey, C.playingDefault); + if(playing)startAudio(); + else showListUI(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { outState.putBoolean(C.playingKey, playing); } + + @Override + public void onDestroy() { + super.onDestroy(); + stopAudio(); + audioTrack.release(); + } + + @Override + public boolean handleMessage(Message msg) { + switch(msg.what) { + case UPDFREQ: + updateFreq(); + return true; + case SETSLOPE: + setSlopeBar(); + return true; + default: + return d.handleMessage(msg); + } + } + + @Override + public void setNewWave(int position) { + if(fm.getBackStackEntryCount() > 0) { + fm.popBackStackImmediate(); + fm.beginTransaction().detach(waveList).commit(); + } + if(position >= 0) { + wave = Enum.valueOf(Wave.class, Wave.names[position]); + sp.edit().putString(C.waveKey, wave.name()).apply(); + } + playing = true; + startAudio(); + } + + void startAudio() { + wave.init(this); + if(audioThread != null) return; + audioThread = new Thread() { + public void run() { + setPriority(Thread.MAX_PRIORITY); + play(); + } + }; + s[0].ready = false; + s[1].ready = false; + audioTrack.play(); + audioThread.start(); + audioIsActive = true; + + new Thread() { public void run() { synthesize(); } }.start(); + } + + void stopAudio() { + if(audioThread == null) return; + audioIsActive = false; + try { audioThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } + audioThread = null; + audioTrack.stop(); + } + + void getValues() { + wave = Enum.valueOf(Wave.class, sp.getString(C.waveKey, C.waveDefault.name())); + amplitude = sp.getFloat(C.amplitudeDef.Key, Double.valueOf(C.amplitudeDef.Default).floatValue()); + mute = sp.getBoolean(C.muteKey, C.muteDefault); + pulseSecs = sp.getFloat(C.pulseDef.Key, Double.valueOf(C.pulseDef.Default).floatValue()); + pauseSecs = sp.getFloat(C.pauseDef.Key, Double.valueOf(C.pauseDef.Default).floatValue()); + slope = sp.getFloat(C.slopeDef.Key, Double.valueOf(C.slopeDef.Default).floatValue()); + freq = sp.getFloat(C.freqDef.Key, Double.valueOf(C.freqDef.Default).floatValue()); + if(freq < C.freqDef.Min) freq = C.freqDef.Min; + freqProgr = sp.getFloat(C.freqProgrDef.Key, Double.valueOf(C.freqProgrDef.Default).floatValue()); + } + + void setControlHandles() { + amplBar = new AmplHandle(d, amplitude); + pulseBar = new PulseHandle(d, pulseSecs); + pauseBar = new PauseHandle(d, pauseSecs); + slopeBar = new SlopeHandle(d, slope); + freqBar = new Handle(d, C.freqDef, freq); + freqProgressBar = new Handle(d, C.freqProgrDef, freqProgr); + + handles = new Handle[] { amplBar, pulseBar, pauseBar, slopeBar, freqBar, freqProgressBar }; + ((ListView)findViewById(R.id.handles)).setAdapter(new Windrover(d, "Controls", this, R.layout.handles_row, handles)); + } + + void showListUI() { + stopAudio(); + playing = false; + waveList = new WavesFragment(); + fm.beginTransaction() + .add(R.id.main, waveList) + .addToBackStack(null) + .commit(); + } + + void getAmpl() { + synchronized(this) { + amplitude = amplBar.getActual(); + setAmplKoef(); + } + } + + void setAmplKoef() { + amplKoef = amplitude; + if(maxY > 1d) amplKoef = amplKoef / maxY; + } + + void setMaxY(double maxY) { + this.maxY = maxY; + setAmplKoef(); + } + + void updateFreq() { + /* zatím se volá z nitě syntetizátoru (netřeba synchronizovat), který zajišťuje pravidelný rytmus */ + double nextTargFreq = targetFreq; + + final double barFreq = freqBar.getActual(); + final double fProgress = freqProgressBar.getActual(); + + if(targetFreq != barFreq) nextTargFreq = barFreq; // změna freqBar má přednost + else if(fProgress != 0d) { + nextTargFreq = targetFreq + fProgress; + if(nextTargFreq > C.freqDef.Max) nextTargFreq = C.freqDef.Max; + if(nextTargFreq < C.freqDef.Min) nextTargFreq = C.freqDef.Min; + freqBar.setActual(nextTargFreq); + } + + if(nextTargFreq != targetFreq) { + synchronized(this) { + targetFreq = nextTargFreq; + deltaFreq = (targetFreq - freq) / 10; + } + if(ll(5)) l(String.format(lo, "freq=%f, targetFreq=%f, deltaFreq=%f", + freq, targetFreq, deltaFreq)); + } + } + + void getPulse() { + pulseCycles = dur2len(pulseBar.getActual()); + pauseCycles = dur2len(pauseBar.getActual()); + Message.obtain(h, SETSLOPE).sendToTarget(); + } + + void setSlopeBar() { + /* slope can't exceed pulse/2 */ + int p = (int)Math.floor(pulseCycles /2); + if(pulseCycles > 0 && slopeBar.getActual() > p) slopeBar.setActual(p); + } + + int dur2len(double duration) { return (int)Math.round(duration * freq); } + + void flipMute() { + mute = !mute; + setMute(); + } + + void setMute() { + ((FlatButton)findViewById(R.id.mute)).setText(mute ? "unmute" : "mute"); + sp.edit().putBoolean(C.muteKey, mute).apply(); + } + + void allocAudio() { + final int audioSessionId = ((AudioManager)getSystemService(Context.AUDIO_SERVICE)).generateAudioSessionId(); + buffsize = AudioTrack.getMinBufferSize( + C.SR, + AudioFormat.CHANNEL_OUT_MONO, + AudioFormat.ENCODING_PCM_16BIT); + l(4, String.format(lo, "buf size=%d", buffsize)); + audioTrack = new AudioTrack( + new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_UNKNOWN) + .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) + .build(), + new AudioFormat.Builder() + .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .build(), + buffsize, + AudioTrack.MODE_STREAM, + audioSessionId); + s = new Samples[] {new Samples(buffsize), new Samples(buffsize)}; + } + + void play() { + while(audioIsActive) { + try { + playSamples(0); + if(audioIsActive) playSamples(1); + } catch(InterruptedException e) {} + } + l(4,"exiting playing thread"); + } + + void playSamples(int y) throws InterruptedException { + synchronized(s[y]) { + while(!s[y].ready && audioIsActive) { + if(ll(7)) l("waiting for samples " + y); + s[y].wait(555); + } + } + if(!audioIsActive) return; + if(ll(7)) l(String.format(lo, "playing samples %d", y)); + int offset = 0, size = buffsize, put; + while((put = audioTrack.write(s[y].samples, offset, size)) < size) { + offset += put; + size -= put; + } + synchronized(s[y]) { + s[y].ready = false; + s[y].notify(); + } + } + + void synthesize() { + phase = 0; + freq = targetFreq = freqBar.getActual(); + dt = C.twoPI * freq / C.SR; + while(audioIsActive) { + try { + compute(0); + if(audioIsActive) compute(1); + } catch(InterruptedException e) {} + // pravidelný interval pro snímání změny frekvence; samotná změna frekvence s provede postupně, když je průběh vlny v uzlu + Message.obtain(h, UPDFREQ).sendToTarget(); + } + } + + void compute(int i) throws InterruptedException { + synchronized(s[i]) { + while(s[i].ready && audioIsActive) s[i].wait(); + } + if(!audioIsActive) return; + computeSamples(i); + synchronized(s[i]) { + s[i].ready = true; + s[i].notify(); + } + } + + void computeSamples(int i) { + for(int j = 0; j < buffsize; j++) { + synchronized(this) { // každý vzorek se synchronizuje zvlášť + if(node) adjustInNode(); + final double k = (mute ? 0 : 1) * slopeKoef * amplKoef; + + s[i].samples[j] = (k != 0 ? (short)(k * wave.value(phase)) : 0); + + phase += dt; + if(phase > C.twoPI) { + phase -= C.twoPI; + node = true; + } + else node = false; + + if(node) { + ++pulsePausePhase; + if(pulsePausePhase > (pulseCycles + pauseCycles)) pulsePausePhase = 0; + } + } + } + } + + void adjustInNode() { + getPulse(); + if(pauseCycles == 0) isInPulse = true; + else isInPulse = ((pulseCycles != 0) && (pulsePausePhase <= pulseCycles)); + if(isInPulse) { + slopeKoef = 1d; + setSlopeBar(); + final double slope = slopeBar.getActual(); + if(slope > 0 && pauseCycles > 0) { + if(pulsePausePhase < slope) + slopeKoef = (pulsePausePhase + 1d) / (slope + 1d); + if(pulsePausePhase > (pulseCycles - 1 - slope)) + slopeKoef = (pulseCycles - pulsePausePhase) / (slope + 1d); + } + } + else slopeKoef = 0; + + if(freq != targetFreq) { + if(Math.abs(freq - targetFreq) < Math.abs(deltaFreq)) freq = targetFreq; + else freq += deltaFreq; + dt = C.twoPI * freq / C.SR; + getPulse(); + } + } +} \ No newline at end of file diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/java/hh/michelson/C.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/java/hh/michelson/C.java Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,46 @@ +package hh.michelson; + +public class C { + /**---------------------------------------------------------- + * constant vector + * ----------------------------------------------------------*/ + + static final int SR = 44100; + /*static final double PI = 4. * Math.atan(1.);*/ + static final double PI = Math.PI; + static final double twoPI = 2. * Math.PI; + static final double quadPI = Math.pow(Math.PI, 2); + static final int maxBarProgress = 999; + static int windowWidth; + + static final String playingKey = "playing"; + static final boolean playingDefault = false; + + static final String waveKey = "WAVE"; + static final Wave waveDefault = Wave.F1; + + static final HandleDef pulseDef = new HandleDef("PULSE", "pulse", 0.1, 3, 0); + static final HandleDef pauseDef = new HandleDef("PAUSE", "pause", 0.1, 3, 0); + static final HandleDef slopeDef = new HandleDef("SLOPE", "slope", 1, 30, 0); + static final HandleDef freqDef = new HandleDef("FREQ", "frequence", 220, 880, 220); + static final HandleDef freqProgrDef = new HandleDef("FREQPROGR", "freq progress", -33, 33, 0); + static final HandleDef amplitudeDef = new HandleDef("AMPLITUDE", "amplitude", 0, 10000, 5000); + + static final String muteKey = "MUTE"; + static final boolean muteDefault = false; +} + +class HandleDef { + String Key; + String Label; + double Min; + double Max; + double Default; + HandleDef(String Key, String Label, double Min, double Max, double Default) { + this.Key = Key; + this.Label = Label; + this.Min = Min; + this.Max = Max; + this.Default = Default; + } +} diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/java/hh/michelson/GraphView.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/java/hh/michelson/GraphView.java Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,93 @@ +package hh.michelson; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import hh.lib.D; + +public class GraphView extends View { + D d; + Context context; + Paint mPaint; + float[] graphValues = new float[] {0f}; + float[] graphLines = null; + int W = 0, H, Y0; + final int maxH = 600; + double maxY = 0d; + double dx; + double kY; + + /*GraphView(Context context) { + super(context); + init(context); }*/ + + public GraphView(Context context, AttributeSet a) { + super(context, a); + init(context); } + + /*GraphView(Context context, AttributeSet a, int defStyleAttr) { + super(context, a, defStyleAttr); + init(context); } + + GraphView(Context context, AttributeSet a, int defStyleAttr, int defStyleRes) { + super(context, a, defStyleAttr, defStyleRes); + init(context); }*/ + + void init(Context context) { + this.d = ((A)context).d.klon(this); + this.context = context; + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setColor(Color.RED);} + + public void onDraw(Canvas canvas) { + d.l(4, "graph.onDraw"); + canvas.drawLine(0, Y0, W, Y0, mPaint); + fillGraphArray(); + if(graphLines != null) canvas.drawLines(graphLines, mPaint); + } + + void setGraphValues(float[] graphValues) { this.graphValues = graphValues; } + + void setMaxY(double maxY) { this.maxY = maxY; } + + protected void onMeasure(int w, int h) { + W = MeasureSpec.getSize(w); + H = MeasureSpec.getSize(h) > maxH ? maxH : MeasureSpec.getSize(h); + super.onMeasure(w, h); + setMeasuredDimension(W, H); + Y0 = H / 2; + if(d.ll(4)) d.l(String.format(A.lo, "graph, onMeasure, w=%d, W=%d, h=%d, H=%d, Y0=%d", + MeasureSpec.getSize(w), W, MeasureSpec.getSize(h), H, Y0)); + } + + void fillGraphArray() { + if(d.ll(4)) d.l(String.format(A.lo, "fillGraphArray, W=%d, graphValues.length=%d", W, graphValues.length)); + graphLines = null; + if(W > 0 && graphValues != null) { + dx = (double)W / graphValues.length; + kY = 4.0 * Y0 / (5.0 * maxY); + graphLines = new float[4 * graphValues.length]; + graphLines[0] = 0; + graphLines[1] = Y0; + if(d.ll(4)) d.l(String.format(A.lo, "W=%d, H=%d, Y0=%d, graphLines.length=%d", W, H, Y0, graphLines.length)); + int i = 0; + for(double v: graphValues) { + if(i > 0) { + graphLines[i] = graphLines[i-2]; + graphLines[i+1] = graphLines[i-1]; + } + graphLines[i+2] = graphLines[i] + Double.valueOf(dx).floatValue(); + graphLines[i+3] = Double.valueOf(Y0 - kY * v).floatValue(); + i += 4; + } + graphLines[0] = graphLines[2]; + graphLines[1] = graphLines[3]; + } + } +} diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/java/hh/michelson/Handle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/java/hh/michelson/Handle.java Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,113 @@ +package hh.michelson; + +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +import hh.lib.D; +import hh.lib.DA; + +class Handle implements SeekBar.OnSeekBarChangeListener, View.OnLongClickListener { + static final String labelDefault = "rowViewRes"; + static final double minDefault = 0f; + static final double maxDefault = 100f; + + String key; + String label = labelDefault; + double min = minDefault; + double max = maxDefault; + double def = min; + volatile double actual = def; + + View handleView; + SeekBar bar; + TextView actualDisp; + TextView labelView; + int listPosition = -1; + + D d; + + Handle(D d, HandleDef def, double actual) { this(d, def.Label, def.Min, def.Max, def.Default, actual, def.Key); } + + Handle(D d, String label, double min, double max, double def, double actual, String key) { + this.d = d.klon(this); + this.label = label; + this.min = min; + this.max = max; + this.def = def; + this.actual = actual; + this.key = key; + } + + /* listener implementation */ + @Override + public void onStopTrackingTouch(SeekBar bar) { saveActual(); } + + @Override + public void onStartTrackingTouch(SeekBar bar) { } + + @Override + public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) { + if(fromUser) { actual = progress2value(bar.getProgress()); } + dispActual(); + } + + @Override + public boolean onLongClick(View v) { + reset(); + return true; + } + + void dispActual() { + actualDisp.setText(String.format(A.lo, "%.2f", actual)); + actualDisp.invalidate(); + } + + double getActual() { return actual; } + + int getActualInt() { return (int)Math.round(actual); } + + void adjustBar() { +// d.l(String.format("+++ adjustBar, key=%s, actual=%f, bar=%d", key, actual, bar.hashCode())); + bar.setProgress(value2progress(actual)); + bar.invalidate(); + } + + void setActual(double actual) { + this.actual = actual; + if(isInflated()) adjustBar(); + saveActual(); + } + + void reset() { setActual(def); } + + void saveActual() { + DA.sp.edit().putFloat(key, Double.valueOf(actual).floatValue()).apply(); } + + boolean isInflated() { + return (handleView != null && handleView.getId() == listPosition); } + + double progress2value(int progress) { return min + (max - min) * progress / C.maxBarProgress; } + + int value2progress(double value) { return (int)Math.round(C.maxBarProgress * (value - min) / (max - min)); } + + void infillView(View handleView, int listPosition) { + this.listPosition = listPosition; + this.handleView = handleView; + handleView.setId(listPosition); + labelView = ((TextView)handleView.findViewById(R.id.label)); + labelView.setText(label); + labelView.setOnLongClickListener(this); + final TextView tmin = (TextView)handleView.findViewById(R.id.min); + if(tmin != null) tmin.setText(String.format(A.lo, "%.1f", min)); + final TextView tmax = (TextView)handleView.findViewById(R.id.max); + if(tmax != null) tmax.setText(String.format(A.lo, "%.1f", max)); + bar = (SeekBar)handleView.findViewById(R.id.bar); + bar.setMax(C.maxBarProgress); + adjustBar(); + actualDisp = handleView.findViewById(R.id.actual); + dispActual(); + bar.setOnSeekBarChangeListener(this); + actualDisp.setOnLongClickListener(this); + } +} diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/java/hh/michelson/RotatedLv.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/java/hh/michelson/RotatedLv.java Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,31 @@ +package hh.michelson; + +import android.content.Context; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.widget.ListView; + +import java.util.Locale; + +import hh.lib.D; + +public class RotatedLv extends ListView { + static String tag; + D d; + + public RotatedLv(Context c) { this(c, null); } + + public RotatedLv(Context c, AttributeSet a) { + super(c, a); + d = new D(c, getClass().getSimpleName() + "." + getId()); + } + + protected void onMeasure(int w, int h) { + super.onMeasure(w, h); + Resources r = d.c.getResources(); + int W = r.getDimensionPixelSize(R.dimen.graph_width); + int H = C.windowWidth - r.getDimensionPixelSize(R.dimen.graph_heigth); + setMeasuredDimension(W, H); + d.l(4, String.format(Locale.getDefault(), "onMeasure, adjusted to width=%d, heigth=%d", W, H)); + } +} diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/java/hh/michelson/Wave.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/java/hh/michelson/Wave.java Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,296 @@ +package hh.michelson; + +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import hh.lib.D; +import hh.lib.DA; + +enum Wave { + SINE { + @Override + double value(double x) { + return Math.sin(x); + } + }, + SAW { + @Override + double value(double x) { + return x / C.PI - 1; + } + }, + TRIAN { + @Override + double value(double x) { + return (x < C.PI ? 2*x/ C.PI - 1 : 2*(1 - x/ C.PI) + 1); + } + }, + SQUARE { + @Override + double value(double x) { + return x < C.PI ? 1 : -1; + } + }, + F1 { + final HandleDef[] F1Def = new HandleDef[] { + new HandleDef(name() + "K00", "N", 1, 30, 3), + new HandleDef(name() + "K01", "K", 0, 2, 1) + }; + + @Override + public String toString() { + return "SUM(N) sin(N * x) / pow(N, K)"; + } + + @Override + void setHandleDef() { paramsDef = F1Def; } + + @Override + void adjustParams() { + n = (int)Math.round(params[0]); + k1 = new double[n]; + for(int i = 1; i < n; i += 2) k1[i] = Math.pow(i, params[1]); + } + + @Override + double value(double x) { + double y = 0; + for(int i = 1; i < n; i += 2) y += Math.sin(i * x) / k1[i]; + return y; + } + }, + F2 { + @Override public String toString () { return "sin(x)+cos(x) + (sin(2x)+cos(2x))/2 + (sin(4x)+cos(4x))/4"; } + + @Override + double value(double x) { + return (Math.sin(x) + Math.cos(x) + 0.5 * Math.sin(2*x) + 0.5 * Math.cos(2*x) + + 0.25 * Math.sin(4*x) + 0.25 * Math.cos(4*x)); + } + }, + F3 { + @Override + public String toString () { return "cos(ln(x))"; } + + @Override + double value(double x) { return Math.cos(Math.log(x)); } + }, + F4 { + @Override public String toString () { return "cos(x^3))"; } + + @Override + double value(double x) { return Math.cos(Math.pow(x, 3)); } + }, + F5 { + @Override public String toString () { return "cos(sin(x)))"; } + + @Override + double value(double x) { return Math.cos(Math.sin(x)); } + }, + F6 { + @Override public String toString () { return "sin(cos(x)))"; } + + @Override + double value(double x) { return Math.sin(Math.cos(x)); } + }, + F7 { + @Override public String toString () { return "sin(sin(x)))"; } + + @Override + double value(double x) { return Math.sin(Math.sin(x)); } + }, + F8 { + @Override public String toString () { return "cos(cos(x)))"; } + + @Override + double value(double x) { return Math.cos(Math.cos(x)); } + }, + F9 { + @Override public String toString () { return "ln(sin(x))"; } + + @Override + double value(double x) { return Math.log(Math.sin(x)); } + }, + F10 { + @Override public String toString () { return "tan(sin(x)+cos(x))"; } + + @Override + double value(double x) { return Math.tan(Math.sin(x) + Math.cos(x)); } + }, + MICHELSON { + final int numOfWaves = 20; + + @Override public String toString () { return "SUMn (Kn * sin(n*x))"; } + + @Override + void setHandleDef() { + paramsDef = new HandleDef[numOfWaves]; + for(int i = 0; i t = new ArrayList<>(); + final List n = new ArrayList<>(); + for (Wave w : Wave.values()) { t.add(w.toString()); n.add(w.name()); } + titles = new String[t.size()]; + t.toArray(titles); + names = new String[n.size()]; + n.toArray(names); + } + + class ParamHandle extends Handle implements SeekBar.OnSeekBarChangeListener { + + ParamHandle(D d, int i) { super(d, paramsDef[i], params[i]); } + + @Override + public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) { + super.onProgressChanged(bar, progress, fromUser); + if(fromUser) { + synchronized(context) { + setParam(listPosition, actual); + adjust(); + } + graphView.invalidate(); + } + } + + @Override + public boolean onLongClick(View v) { + final boolean r = super.onLongClick(v); + synchronized(context) { + setParam(listPosition, actual); + adjust(); + } + graphView.invalidate(); + return r; + } + + @Override + void infillView(View handleView, int listPosition) { + super.infillView(handleView, listPosition); + labelView.setOnLongClickListener(this); + bar.setOnSeekBarChangeListener(this); + } + } + + String tag = getClass().getName(); + + D d; + A context; + int n; + double k1[]; + double[] params = null; + HandleDef[] paramsDef = null; + ParamHandle[] paramHandles; + RotatedLv paramView; + + GraphView graphView; + float[] graphValues; + double maxY = 0d; + + double value(double x) { + return 0.0d; + } + + boolean hasParams() { return params != null; } + + void init(A context) { + this.context = context; + this.d = context.d.klon(this); + ((TextView)context.findViewById(R.id.dispWave)).setText(String.format("%s", this)); + paramView = context.findViewById(R.id.params); + graphView = context.findViewById(R.id.graph); + graphValues = new float[(int)Math.ceil(A.rs.getDimension(R.dimen.graph_width))]; + /*graphValues = new double[1 + (int)Math.ceil(2 * D.SR / freq)];*/ + graphView.setGraphValues(graphValues); + initParams(); + adjust(); + setParamsView(); + graphView.invalidate(); + } + + void adjust() { + adjustParams(); + computeWaveGraph(); + } + + void setHandleDef() {} + + void initParams() { + if(params == null) { + setHandleDef(); + if(paramsDef != null) { + params = new double[paramsDef.length]; + for(int i = 0; i < params.length; i++) + params[i] = DA.sp.getFloat(paramsDef[i].Key, Double.valueOf(paramsDef[i].Default).floatValue()); + } + } + } + + void adjustParams() {} // předzpracování parametrů vlny po změně nastavitelného parametru + + void setParam(int i, double value) { params[i] = value; } + + void resetParams() { + if(hasParams()) { + for(int i=0; i < params.length; i++) paramHandles[i].reset(); + synchronized(context) { + for(int i = 0; i < params.length; i++) setParam(i, paramsDef[i].Default); + adjust(); + } + graphView.invalidate(); + } + } + + void setParamsView() { + final View reset = context.findViewById(R.id.reset); + if(hasParams()) { + reset.setVisibility(View.VISIBLE); + reset.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + resetParams(); + } + }); + setParamHandles(); + paramView.setAdapter(new Windrover(d, "Params", context, R.layout.wave_params_row, paramHandles)); + } + else { + reset.setVisibility(View.GONE); + paramView.setAdapter(null); + } + } + + void setParamHandles() { + paramHandles = new ParamHandle[params.length]; + for(int i = 0; i < params.length; i++) { paramHandles[i] = new ParamHandle(d, i); } + } + + void computeWaveGraph() { + final double dt = 2 * C.twoPI / graphValues.length; /*dt = D.twoPI * freq / D.SR,*/ + double t = 0d; + maxY = 0d; + for (int i=0; i maxY) maxY = Math.abs(v); + graphValues[i] = Double.valueOf(v).floatValue(); + t += dt; + } + graphView.setMaxY(maxY); + context.setMaxY(maxY); + } +} diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/java/hh/michelson/WavesFragment.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/java/hh/michelson/WavesFragment.java Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,45 @@ +package hh.michelson; + +import android.app.Activity; +import android.app.ListFragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +public class WavesFragment extends ListFragment { + OnFragmentInteractionListener mListener; + boolean selected = false; + + @Override + public void onAttach(Activity context) { + super.onAttach(context); + setListAdapter(new ArrayAdapter<>(getActivity(), R.layout.wave_list_row, Wave.titles)); + mListener = (OnFragmentInteractionListener)context; + setRetainInstance(true); // zachovat fragment při otáčení obrazovky + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.list_view, container, false); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + mListener.setNewWave(position); + selected = true; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if(!selected) mListener.setNewWave(-1); + mListener = null; + } + + public static interface OnFragmentInteractionListener { + public void setNewWave(int position); + } +} diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/java/hh/michelson/Windrover.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/java/hh/michelson/Windrover.java Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,45 @@ +package hh.michelson; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListAdapter; + +import hh.lib.D; + +class Windrover extends BaseAdapter implements ListAdapter { + D d; + Context c; + int rowViewRes; + Handle[] handles; + + Windrover(D d, String label, Context c, int rowViewRes, Handle[] handles) { + this.d = d.klon(getClass().getSimpleName() + "." + label); + this.c = c; + this.rowViewRes = rowViewRes; + this.handles = handles; + } + + public Object getItem(int pos) { + if(d.ll(4)) d.l(String.format("getItem=%s", handles[pos])); + return handles[pos]; + } + + public long getItemId(int pos) { + if(d.ll(4)) d.l(String.format(A.lo, "getItemId=%d", pos)); + return pos; + } + + public int getCount() { + if(d.ll(4)) d.l(String.format(A.lo, "getCount=%d", handles.length)); + return handles.length; + } + + public View getView(int pos, View handleView, ViewGroup parent) { + View v = ((LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(rowViewRes, null); + handles[pos].infillView(v, pos); + return v; + } +} diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/res/layout/handles_row.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/res/layout/handles_row.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/res/layout/header.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/res/layout/header.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + \ No newline at end of file diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/res/layout/list_view.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/res/layout/list_view.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,9 @@ + + \ No newline at end of file diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/res/layout/main.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/res/layout/main.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/res/layout/wave.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/res/layout/wave.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/res/layout/wave_list_row.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/res/layout/wave_list_row.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/res/layout/wave_params_row.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/res/layout/wave_params_row.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,55 @@ + + + + + + + + + + + + + diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/res/values/dimens.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/res/values/dimens.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,8 @@ + + + 320px + 320px + 60px + 15px + 20px + \ No newline at end of file diff -r 000000000000 -r 16509f98f301 android/michelson.studio/michelson/src/main/res/values/strings.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/michelson/src/main/res/values/strings.xml Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,4 @@ + + + .michelson.17 + diff -r 000000000000 -r 16509f98f301 android/michelson.studio/settings.gradle --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/michelson.studio/settings.gradle Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,1 @@ +include ':michelson', ':lib031-release' diff -r 000000000000 -r 16509f98f301 python/michelson.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/python/michelson.py Fri Nov 22 09:40:16 2019 +0100 @@ -0,0 +1,103 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import sys, math +from PyQt5.QtWidgets import QWidget, QApplication, QSlider, QVBoxLayout, QPushButton +from PyQt5.QtGui import QPainter, QPen, QStaticText +from PyQt5.QtCore import Qt + +pos = (300, 300) +size = 300 +margin = 10 + +class Michelson(QWidget): + + def __init__(self): + super().__init__() + + self.setGeometry(pos[0], pos[1], size + 20 * 40 + 0, size) + self.setWindowTitle('Michelson') + + self.s = [] + for i in range(0, 20): + self.s.append(SliderK(self, i)) + self.s[i].setGeometry(size + i*40, margin, 40, size - 2*margin) + + self.show() + + def paintEvent(self, e): + qp = QPainter() + qp.begin(self) + self.drawGraph(qp) + qp.end() + + def drawGraph(self, qp): + vmax, v = self.values() + y0 = (size - 2*margin) / 2 + margin + yk = (y0 - margin) / vmax if vmax > 0 else 0 + qp.setPen(QPen(Qt.black, 1, Qt.SolidLine)) + qp.drawLine(0, y0, size, y0) + qp.setPen(QPen(Qt.green, 2, Qt.SolidLine)) + for i in range(0, len(v) - 1): + qp.drawLine(margin + i, y0 - yk * v[i], margin + i + 1, y0 - yk * v[i+1]) + + def values(self): + vmax = 0 + v = [] + x = 0 + dx = 2 * math.pi / (size - 2 * margin) + while x <= 2 * math.pi: + y = 0 + for i in range(0, 20): + y = y + self.s[i].v * math.sin((i + 1) * x) +# y = math.sqrt(math.pi * math.pi - (x-math.pi) * (x-math.pi)) + if math.fabs(y) > vmax: vmax = math.fabs(y) + v.append(y) + x = x + dx + return (vmax, v) + + +class SliderK(QWidget): + + def __init__(self, w, i): + super().__init__(w) + + self.w = w + self.i = i + self.init = 0 + self.v = 0 + + label = QPushButton(str(i+1)) + + self.val = QPushButton(str(self.init)) + self.val.clicked.connect(self.resetK) + + self.sld = QSlider(Qt.Vertical, self.w) + self.sld.setFocusPolicy(Qt.NoFocus) + self.sld.setMaximum(20) + self.sld.setMinimum(-20) + self.sld.setValue(self.init) + self.sld.valueChanged[int].connect(self.changeK) + + vbox = QVBoxLayout() + + vbox.addWidget(label) + vbox.addWidget(self.val) + vbox.addWidget(self.sld) + + self.setLayout(vbox) + + def changeK(self, v): +# print("changeK({})={}".format(self.i, v)) + self.v = v + self.val.setText(str(v)) + self.w.update() + + def resetK(self): + self.sld.setValue(self.init) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + widget = Michelson() + sys.exit(app.exec_())