Skip to content

Commit

Permalink
Add PoC of the pitch picker with custom vertical slider and playing a…
Browse files Browse the repository at this point in the history
… particular frequency on Android
  • Loading branch information
Transfusion committed Aug 3, 2019
1 parent 0f3db0a commit d61619c
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 8 deletions.
5 changes: 4 additions & 1 deletion app/components/App.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- I assume this will be the landing page for users to navigate to settings and exercises. -->

<template>
<Page actionBarHidden="true" class="page">
<Page @loaded="onPageLoaded" actionBarHidden="true" class="page">
<FlexboxLayout style="flex: 1;" flexDirection="column" id="container">
<TextView :text="'Project\nSpectra'" editable="false" id="project-spectra-title"/>

Expand Down Expand Up @@ -59,6 +59,9 @@
}
},
methods: {
onPageLoaded: function(args) {
console.log(this.$store.state);
},
onExercises() {
this.$navigateTo(ActiveExercises);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,6 @@
},
onNavigatedTo: function(args) {
console.log('janoasjdfoiasdf');
console.log(args);
}
}
Expand Down
46 changes: 40 additions & 6 deletions app/components/OnboardingScreens/SetAGoal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<RadDataForm style="background-color: #C9C9C9" :source="form" :metadata="formMetadata"
@propertyCommitted="onFormPropertyCommitted" />

<check-box text='Select a specific pitch' checkPadding="25dp" :style="checkboxStyle"
:fillColor="Config.primaryColor" @checkedChange="onSpecificPitchCheck($event)"
checked="false" />

<Label text='You can change this at any time in Settings'
style="font-style: italic"/>

Expand All @@ -19,7 +23,7 @@

<FlexboxLayout justifyContent="space-between" flexDirection="row">
<SpectraActionButton type='warning' text="Return" @tap="onReturn" />
<SpectraActionButton text="All Set :)" @tap="onOK" />
<SpectraActionButton :text="nextButtonText" @tap="onOK" />
</FlexboxLayout>

</FlexboxLayout>
Expand All @@ -33,9 +37,9 @@
import SpectraActionButton from "@/components/UIControls/SpectraActionButton";
import SpectraTextView from "@/components/UIControls/SpectraTextView";
import {AVAILABLE_GOALS} from "@/utils/Constants";
import {Config} from "@/utils/Config";
import App from "@/components/App";
import SpecificPitchPicker from "@/components/OnboardingScreens/SpecificPitchPicker";
import {PropertyConverter} from 'nativescript-ui-dataform'
/*Sun May 05 2019 01:54:28 GMT+0800 Transfusion: the converter property which transforms the objects backing
Expand All @@ -62,8 +66,13 @@
methods: {
onOK: function(){
if (this.form.goal === 'Sample Goal') {this.form.goal = 'sample_goal';}
this.$store.commit("save", {...this.$store.state, goal: this.form.goal, firstLoad: false});
this.$navigateTo(App, {clearHistory: true});
this.$store.commit("save", {...this.$store.state, goal: this.form.goal,
firstLoad: this.selectSpecificPitchChecked});
if (this.selectSpecificPitchChecked) {
this.$navigateTo(SpecificPitchPicker);
} else {
this.$navigateTo(App, {clearHistory: true});
}
},
onReturn: function(){
Expand All @@ -74,10 +83,35 @@
onFormPropertyCommitted: function(data) {
let editedObject = JSON.parse(data.object.editedObject);
this.form.goal = AVAILABLE_GOALS.find((goal) => goal.name === editedObject.goal).id
},
onSpecificPitchCheck: function($event) {
// https://github.com/bradmartin/nativescript-checkbox
this.selectSpecificPitchChecked = $event.value;
console.log(this.selectSpecificPitchChecked);
}
},
computed: {
checkboxStyle(){
return {
"font-size": '17em',
// "font-style": "italic",
}
},
nextButtonText() {
if (this.selectSpecificPitchChecked) {
return "Next"
} else {
return "All Set :)"
}
}
},
data() {
return {form: {
return {
Config,
selectSpecificPitchChecked: false,
form: {
goal: 'Sample Goal'
}, formMetadata: {
'isReadOnly': false,
Expand Down
272 changes: 272 additions & 0 deletions app/components/OnboardingScreens/SpecificPitchPicker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
<template>

<Page @loaded="onPageLoaded"
actionBarHidden="true" class="page">

<FlexboxLayout style="flex: 1;" flexDirection="column" id="container">
<TextView text="Set A Goal" editable="false" id="welcome"/>

<TextView editable="false" id="set-a-goal-desc">
<Span :text="WELCOME_TEXT"/>
</TextView>

<FlexboxLayout flexDirection="row" id="container" :minHeight="sliderAreaHeight">
<!--fixed width-->
<FlexboxLayout style="width: 55%; align-items: flex-end; justify-content: center" flexDirection="column" id="container">

<TextView editable="false" id="selectedPitchLegend">
<Span :text=" this.currentNote + '/' + this.currentPitchHz + ' Hz'" style="font-size: 25em; font-weight: bold" />
<Span :text="'\n'" />
<Span :text="`(${currentPitchTextDescription})`" />
</TextView>
</FlexboxLayout>

<!--<FlexboxLayout style="flex: 1; align-items: center;" flexDirection="column" id="container">-->
<AbsoluteLayout backgroundColor="transparent" minWidth="50">


<StackLayout backgroundColor="white" left="30" minWidth="20" borderColor="black"
borderWidth="2dp" :height="sliderAreaHeight"/>
<StackLayout @pan="onSliderPan" backgroundColor="black" :top="sliderOffset" width="50"
:height="sliderKnobHeight"/>
</AbsoluteLayout>
<!--</FlexboxLayout>-->

<FlexboxLayout style="flex: 1; align-items: center;" flexDirection="column" >
<!--<TextView text="show up!" editable="false" id="welcome"/>-->
<StackLayout flexGrow="1" backgroundColor="#B6A9D7" style="width: 60%;"/>
<StackLayout flexGrow="1" backgroundColor="#BF99D7" style="width: 60%;"/>
<StackLayout flexGrow="1" backgroundColor="#D7B7DF" style="width: 60%;"/>
</FlexboxLayout>
</FlexboxLayout>

<FlexboxLayout style="justify-content: center;">
<FlexboxLayout flexDirection="row" justifyContent="space-between" id="play-this-pitch"
@tap="playThisPitchTap" >
<Label text="Play this pitch" alignSelf="center" style="font-weight: bold; font-size: 20em" />
<StackLayout width="10dp"/>
<Label :text="'\uf028'" alignSelf="center" style="font-size: 20em;" class="fas" />
</FlexboxLayout>
</FlexboxLayout>


<!--Empty placeholder (there is no equivalent of div in NS..) -->
<StackLayout style="flex-grow: 1"/>

<FlexboxLayout justifyContent="space-between" flexDirection="row">
<SpectraActionButton type='warning' text="Return" @tap="onReturn" />
<SpectraActionButton text="All Set :)" @tap="onOK" />
</FlexboxLayout>

</FlexboxLayout>

</Page>
</template>

<script>
// the range that the slider is supposed to represent
// https://languagelog.ldc.upenn.edu/nll/?p=5908
import {PanGestureEventData} from "tns-core-modules/ui/gestures";
const MIN_PITCH_HZ = 85;
const ANDROGYNOUS_BOTTOM_BORDER = 120;
const ANDROGYNOUS_TOP_BORDER = 160;
const MAX_PITCH_HZ = 220;
const AUDIO_SAMPLE_DURATION = 5;
const SLIDER_KNOB_HEIGHT = 16;
import * as platformModule from 'tns-core-modules/platform';
import App from "@/components/App";
import SpectraActionButton from "@/components/UIControls/SpectraActionButton";
import SpectraTextView from "@/components/UIControls/SpectraTextView";
import {noteFromPitch} from '@/utils/Utils';
import {isAndroid} from "tns-core-modules/platform";
export default {
/*mounted() {
let sliderContainer = this.$refs.sliderContainer;
console.log(sliderContainer);
sliderContainer.nativeView.setClipChildren(false);
},*/
onNavigatedTo: function(args) {
console.log(platformModule.screen.mainScreen.heightDIPs * 0.5);
},
components: {
SpectraActionButton,
SpectraTextView
},
data() {
return {
name: '',
WELCOME_TEXT:
"Since you said you wanted to aim for a specific pitch, select it below.\n" +
"Remember, pitch isn't everything, so choosing a manageable goal is best.",
sliderProgress: 0.5,
}
},
computed: {
sliderKnobHeight: () => SLIDER_KNOB_HEIGHT,
sliderAreaHeight: () => platformModule.screen.mainScreen.heightDIPs * 0.4,
currentNote: function () {
return noteFromPitch(this.currentPitchHz);
},
currentPitchHz: function() {
// math.round is for https://stackoverflow.com/questions/11832914/round-to-at-most-2-decimal-places-only-if-necessary
return Math.round((this.sliderProgress * (MAX_PITCH_HZ - MIN_PITCH_HZ) + MIN_PITCH_HZ) * 100) / 100;
},
sliderOffset: function() {
/*let percentage = (this.currentPitchHz - MIN_PITCH_HZ) / (MAX_PITCH_HZ - MIN_PITCH_HZ);
if (percentage < 0){percentage = 0}
if (percentage > 1){percentage = 1}
percentage = 1 - percentage;*/
return (1 - this.sliderProgress )* (this.sliderAreaHeight - SLIDER_KNOB_HEIGHT)
},
currentPitchTextDescription: function() {
if (this.currentPitchHz < ANDROGYNOUS_BOTTOM_BORDER){
return 'male range';
}
else if (this.currentPitchHz < ANDROGYNOUS_TOP_BORDER) {
return 'androgynous range';
}
else {
return 'female range';
}
}
},
methods: {
playThisPitchTap: function(args) {
console.log("playing note!");
if (isAndroid) {
let duration = AUDIO_SAMPLE_DURATION; // seconds
let sampleRate = 22050;
let numSamples = duration * sampleRate;
let samples = [];
// let buffer = [];
let buffer = Array.create("short", numSamples);
for (let i = 0; i < numSamples; ++i)
{
samples.push( Math.sin(2 * Math.PI * i / (sampleRate / this.currentPitchHz)) ); // Sine wave
buffer[i] = samples[i] * 32767; // Higher amplitude increases volume
}
let audioTrack = new android.media.AudioTrack(android.media.AudioManager.STREAM_MUSIC,
sampleRate, android.media.AudioFormat.CHANNEL_OUT_MONO,
android.media.AudioFormat.ENCODING_PCM_16BIT, buffer.length,
android.media.AudioTrack.MODE_STATIC);
audioTrack.write(buffer, 0, buffer.length);
audioTrack.play();
}
else {
alert('Not implemented for your platform yet!');
}
},
onPageLoaded: function(args) {
/*console.log("navigated to the pitch picker");
let page = args.object;
let sliderContainer = page.getViewById('sliderContainer');
console.log(sliderContainer);
console.log('grid clipChildren: ' + sliderContainer.android.getClipChildren());
sliderContainer.android.getParent().setClipChildren(false);
console.log('grid clipChildren: ' + sliderContainer.android.getClipChildren()); */
},
onOK: function(){
this.$store.commit("save", {...this.$store.state, specificPitchGoal: this.currentPitchHz, firstLoad: false});
this.$navigateTo(App, {clearHistory: true});
},
onReturn: function(){
this.$navigateBack();
},
onSliderPan(args) {
if (args.state === 1) // down
{
// this.prevDeltaX = 0;
this.prevDeltaY = 0;
}
else if (args.state === 2) // panning
{
/*this.dragImageItem.translateX += args.deltaX - this.prevDeltaX;
this.dragImageItem.translateY += args.deltaY - this.prevDeltaY;*/
let delta = (args.deltaY - this.prevDeltaY);
// console.log(delta);
this.sliderProgress = this.sliderProgress -
delta / this.sliderAreaHeight;
if (this.sliderProgress < 0){this.sliderProgress = 0}
if (this.sliderProgress > 1){this.sliderProgress = 1}
// this.prevDeltaX = args.deltaX;
this.prevDeltaY = args.deltaY;
}
else if (args.state === 3) // up
{
}
}
/*onSliderLoaded(event) {
console.log(event);
const layout = event.object;
if (layout.android) {
layout.android.getParent().setClipChildren(false);
}
}*/
}
}
</script>


<style scoped>
Page {
background-color: #E8E8E8;
}
#play-this-pitch {
background-color: #C98DD8;
padding: 15dp;
/*min-width: 80dp;*/
/*width: 70dp;*/
border-radius: 5dp;
box-shadow: 5px 5px 10px;
}
#container {
padding: 15em;
}
#welcome {
font-size: 20em;
font-family: serif;
color: #000;
font-weight: 700;
background-color: transparent;
}
#set-a-goal-desc {
border-width: 0;
background-color: transparent;
border-color: #E8E8E8;
}
#selectedPitchLegend {
border-width: 0;
background-color: transparent;
border-color: #E8E8E8;
text-align: right;
}
</style>
8 changes: 8 additions & 0 deletions app/utils/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ export const getFrequency = function (note) {
return 440 * Math.pow(2, (keyNumber- 49) / 12);
};

let noteStrings = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];

// todo: refactor with divmod
export function noteFromPitch( frequency ) {
let noteNum = 12 * (Math.log(frequency / 440) / Math.log(2));
let note = Math.round( noteNum ) + 69;
return noteStrings[note % 12] + (Math.floor(note / 12) - 1)
}

export const MathUtils = {
interpolateLinear: (X1, X2, Y1, Y2) => {
Expand Down
Loading

0 comments on commit d61619c

Please sign in to comment.