From f03a2139bb8e4a69da015bb84cc7583ac530257f Mon Sep 17 00:00:00 2001 From: Jennifer Mankoff Date: Tue, 22 Feb 2022 18:05:24 -0800 Subject: [PATCH 01/29] Buggy alternative way to do slope --- .../handler/SlopeParityHandler.ts | 58 +++---------------- src/sonification/output/FileOutput.ts | 47 ++++++++------- src/sonification/output/NoteSonify.ts | 18 +++--- src/sonification/stat/RangeEndExpander.ts | 2 +- src/sonification/stat/RunningAverage.ts | 4 +- src/sonification/stat/SlopeChange.ts | 55 ++++++++++++++++++ src/sonification/stat/Statistic.ts | 2 +- 7 files changed, 98 insertions(+), 88 deletions(-) create mode 100644 src/sonification/stat/SlopeChange.ts diff --git a/src/sonification/handler/SlopeParityHandler.ts b/src/sonification/handler/SlopeParityHandler.ts index 3fa2318..062cc81 100644 --- a/src/sonification/handler/SlopeParityHandler.ts +++ b/src/sonification/handler/SlopeParityHandler.ts @@ -1,4 +1,4 @@ -import { filter, Observable, tap } from 'rxjs' +import { filter, map, Observable, tap, withLatestFrom } from 'rxjs' import { DatumOutput } from '../output/DatumOutput' import { getSonificationLoggingLevel, OutputStateChange, SonificationLoggingLevel } from '../OutputConstants' import { DataHandler } from './DataHandler' @@ -10,28 +10,6 @@ const DEBUG = true * @todo change this to take a function that decides how to filter? */ export class SlopeParityHandler extends DataHandler { - /** - * The slope between the previous two points. - */ - private _prevSlope: number - public get prevSlope(): number { - return this._prevSlope - } - public set prevSlope(value: number) { - this._prevSlope = value - } - - /** - * The previous data point, used to calculate current slope. - */ - private _prevPoint: number - public get prevPoint(): number { - return this._prevPoint; - } - public set prevPoint(value: number) { - this._prevPoint = value - } - private _direction: number public get direction(): number { return this._direction @@ -49,8 +27,6 @@ export class SlopeParityHandler extends DataHandler { */ constructor(output?: DatumOutput, direction?: number) { super(output) - this._prevPoint = 0 - this._prevSlope = 0 if (direction) { this._direction = direction } else { @@ -64,32 +40,15 @@ export class SlopeParityHandler extends DataHandler { * * @param sink The sink that is producing data for us */ - public setupSubscription(sink$: Observable) { - debugStatic (SonificationLoggingLevel.DEBUG, `setting up subscription for ${this} ${sink$}`) + public setupSubscription(sink$: Observable) { + debugStatic(SonificationLoggingLevel.DEBUG, `setting up subscription for ${this} ${sink$}`) + let slope$ = new SlopeChange(3, sink$) + super.setupSubscription( sink$.pipe( - filter((val) => { - if (val instanceof Datum){ - let slope = val.value - this.prevPoint - this.prevPoint = val.value // no matter what, we'll need the prev point to calculate the slope - if (this.direction == 0) { - console.log("direction 0") - if (Math.sign(slope) != Math.sign(this.prevSlope)) { - if (DEBUG) console.log('direction of slope changed') - this.prevSlope = slope - return true - } - return false - } else { - if (Math.sign(slope) == this.direction) { - if (DEBUG) console.log("slope matching direction", this.direction) - return true - } - return false - } - } - else return true - }), + withLatestFrom(slope$), + filter((vals) => vals[1] != this.direction), + map((vals) => vals[0]), ), ) } @@ -105,6 +64,7 @@ export class SlopeParityHandler extends DataHandler { //////////// DEBUGGING ////////////////// import { tag } from 'rxjs-spy/operators/tag' import { Datum } from '../Datum' +import { SlopeChange } from '../stat/SlopeChange' const debug = (level: number, message: string, watch: boolean) => (source: Observable) => { if (watch) { return source.pipe( diff --git a/src/sonification/output/FileOutput.ts b/src/sonification/output/FileOutput.ts index 2ba2cc9..7e47ad9 100644 --- a/src/sonification/output/FileOutput.ts +++ b/src/sonification/output/FileOutput.ts @@ -1,35 +1,34 @@ -import { Datum } from "../Datum"; -import { SonifyFixedDuration } from "./SonifyFixedDuration"; +import { Datum } from '../Datum' +import { SonifyFixedDuration } from './SonifyFixedDuration' /** * Class for sonifying a data point using an array buffer. * @extends SonifyFixedDuration */ export class FileOutput extends SonifyFixedDuration { + private buffer: ArrayBuffer | undefined - private buffer : ArrayBuffer | undefined; - - constructor(buffer? : ArrayBuffer) { - super() - if (buffer) { - this.buffer = buffer + constructor(buffer?: ArrayBuffer) { + super() + if (buffer) { + this.buffer = buffer + } } - } - protected create(datum: Datum): AudioScheduledSourceNode { - console.log("playing for datum", datum.value) - const source = FileOutput.audioCtx.createBufferSource() - if (this.buffer) { - FileOutput.audioCtx.decodeAudioData(this.buffer.slice(0), (buffer) => source.buffer = buffer) - this.outputNode = source - this.outputNode.connect(this.gainNode) - source.start() + protected create(datum: Datum): AudioScheduledSourceNode { + console.log('playing for datum', datum.value) + const source = FileOutput.audioCtx.createBufferSource() + if (this.buffer) { + FileOutput.audioCtx.decodeAudioData(this.buffer.slice(0), (buffer) => (source.buffer = buffer)) + this.outputNode = source + this.outputNode.connect(this.gainNode) + source.start() + } + if (source.buffer) this.duration = source.buffer.duration + return source } - if (source.buffer) this.duration = source.buffer.duration - return source - } - protected extend(timeAdd: number) { - throw new Error("Method not implemented."); - } -} \ No newline at end of file + protected extend(timeAdd: number) { + throw new Error('Method not implemented.') + } +} diff --git a/src/sonification/output/NoteSonify.ts b/src/sonification/output/NoteSonify.ts index 2f03d03..e80d3ef 100644 --- a/src/sonification/output/NoteSonify.ts +++ b/src/sonification/output/NoteSonify.ts @@ -3,10 +3,8 @@ import { getSonificationLoggingLevel, SonificationLoggingLevel } from '../Output import { Sonify } from './Sonify' import { Observable, tap } from 'rxjs' - const DEBUG = false - /** * Class for sonifying a data point as a pitch. * @extends Sonify @@ -43,17 +41,17 @@ export class NoteSonify extends Sonify { protected output(datum: Datum) { debugStatic(SonificationLoggingLevel.DEBUG, `outputing ${datum.value} to oscillator`) let oscillator = this.outputNode as OscillatorNode - if(!this.isAudioPlaying) { - oscillator.start() - this.isAudioPlaying = true + if (!this.isAudioPlaying) { + oscillator.start() + this.isAudioPlaying = true + } + oscillator.frequency.value = datum.value } - oscillator.frequency.value = datum.value -} /** * Generates a new note sonifier */ - public constructor(pan:number=0) { - super(Sonify.audioCtx.createOscillator(),pan) + public constructor(pan: number = 0) { + super(Sonify.audioCtx.createOscillator(), pan) let oscillator = this.outputNode as OscillatorNode if (oscillator == undefined) { @@ -94,11 +92,9 @@ const debug = (level: number, message: string, watch: boolean) => (source: Obser } const debugStatic = (level: number, message: string) => { - if (DEBUG) { if (level >= getSonificationLoggingLevel()) { console.log(message) } //else console.log('debug message dumped') } - } diff --git a/src/sonification/stat/RangeEndExpander.ts b/src/sonification/stat/RangeEndExpander.ts index 4ff64c2..323972e 100644 --- a/src/sonification/stat/RangeEndExpander.ts +++ b/src/sonification/stat/RangeEndExpander.ts @@ -29,7 +29,7 @@ export class RangeEndExpander extends Statistic { * @param stream$ the stream of data * @returns a subscription */ - public setupSubscription(stream$: Observable): Subscription { + protected setupSubscription(stream$: Observable): Subscription { return super.setupSubscription( stream$.pipe( reduce((acc, curr) => { diff --git a/src/sonification/stat/RunningAverage.ts b/src/sonification/stat/RunningAverage.ts index 37270bb..0af5e7e 100644 --- a/src/sonification/stat/RunningAverage.ts +++ b/src/sonification/stat/RunningAverage.ts @@ -6,7 +6,7 @@ import { Statistic } from './Statistic' /** * Calculates a running average based on the last n values seen. */ -class RunningAverage extends Statistic { +export class RunningAverage extends Statistic { /** * What buffer should the average be calculated over? */ @@ -22,7 +22,7 @@ class RunningAverage extends Statistic { this.buffer = len ? len : this.buffer } - public setupSubscription(stream$: Observable): Subscription { + protected setupSubscription(stream$: Observable): Subscription { return super.setupSubscription( stream$.pipe( bufferCount(this.buffer), diff --git a/src/sonification/stat/SlopeChange.ts b/src/sonification/stat/SlopeChange.ts new file mode 100644 index 0000000..28a46af --- /dev/null +++ b/src/sonification/stat/SlopeChange.ts @@ -0,0 +1,55 @@ +import { bufferCount, combineLatest, map, mergeMap, Observable, reduce, Subscription, take, windowCount } from 'rxjs' +import { Datum } from '../Datum' +import { OutputStateChange } from '../OutputConstants' +import { Statistic } from './Statistic' + +/** + * Calculates a running average based on the last n values seen. + */ +export class SlopeChange extends Statistic { + /** + * What buffer should the slope average be calculated over? + */ + buffer = 3 + + /** + * + * @param stream$ The stream of data over which to calculate the statistic + * @param len The number of data points to calculate the running average over + */ + constructor(len: number, stream$: Observable) { + super(0, stream$) + this.buffer = len ? len : this.buffer + } + + protected setupSubscription(stream$: Observable): Subscription { + let windows$ = stream$.pipe(windowCount(this.buffer)) + return super.setupSubscription( + windows$.pipe( + bufferCount(2), + mergeMap((frames) => { + const total1$ = frames[0].pipe( + reduce((acc, curr) => { + acc += curr + return acc + }, 0), + take(1), + ) + const total2$ = frames[1].pipe( + reduce((acc, curr) => { + acc += curr + return acc + }, 0), + take(1), + ) + return combineLatest([total1$, total2$]).pipe( + map((totals) => { + const diff = 1 / (totals[0] / this.buffer) - 1 / (totals[1] / this.buffer) + return diff > 0 ? 1 : diff == 0 ? 0 : -1 + }), + ) + }), + ), + ) + } +} diff --git a/src/sonification/stat/Statistic.ts b/src/sonification/stat/Statistic.ts index c0715b9..035cb2e 100644 --- a/src/sonification/stat/Statistic.ts +++ b/src/sonification/stat/Statistic.ts @@ -37,7 +37,7 @@ export class Statistic extends BehaviorSubject { * * @param stream$ The data being streamed */ - public setupSubscription(stream$: Observable): Subscription { + protected setupSubscription(stream$: Observable): Subscription { this.subscription?.unsubscribe() this.subscription = stream$.subscribe(this) return this.subscription From 0e18f8c31532438e02b734080ca16a08a7210570 Mon Sep 17 00:00:00 2001 From: Jennifer Mankoff Date: Sat, 5 Mar 2022 21:43:51 -0800 Subject: [PATCH 02/29] Added a slope demo that plays a tone representing the current slope --- src/pages/Demo.tsx | 16 ++-- src/sonification/handler/DataHandler.ts | 39 +++----- src/sonification/handler/ScaleHandler.ts | 6 -- src/sonification/handler/SlopeHandler.ts | 89 +++++++++++++++++++ .../handler/SlopeParityHandler.ts | 5 +- src/sonification/output/NoteSonify.ts | 2 +- src/sonification/stat/Slope.ts | 73 +++++++++++++++ src/sonification/stat/SlopeChange.ts | 86 +++++++++++------- src/views/demos/DemoSlope.tsx | 87 ++++++++++++++++++ 9 files changed, 333 insertions(+), 70 deletions(-) create mode 100644 src/sonification/handler/SlopeHandler.ts create mode 100644 src/sonification/stat/Slope.ts create mode 100644 src/views/demos/DemoSlope.tsx diff --git a/src/pages/Demo.tsx b/src/pages/Demo.tsx index 31ad9b7..24243f0 100644 --- a/src/pages/Demo.tsx +++ b/src/pages/Demo.tsx @@ -18,17 +18,23 @@ import { DemoFileOutput } from '../views/demos/DemoFileOutput' import { op } from 'arquero' import { DemoSlopeParityV1 } from '../views/demos/DemoSlopeParityV1' import { DemoSlopeParityV2 } from '../views/demos/DemoSlopeParityV2' +import { DemoSlope } from '../views/demos/DemoSlope' import { DemoRunningExtrema } from '../views/demos/DemoRunningExtrema' import Table from 'arquero/dist/types/table/table' const DEMO_VIEW_MAP = { simple: { value: 'simple', label: 'Simple sonification', component: DemoSimple }, highlightRegion: { value: 'highlightRegion', label: 'Highlight points for region', component: DemoHighlightRegion }, - speechHighlight: { value: 'speechHighlight', label: 'Speak points in range', component: DemoSpeakRange}, - fileOutput: {value: 'fileOutput', label: 'Point of interest notification', component: DemoFileOutput}, - slopeParityV1: {value: 'slopeParityV1', label: 'Slope direction change notification', component: DemoSlopeParityV1}, - slopeParityV2: {value: 'slopeParityV2', label: 'Slope parity notification', component: DemoSlopeParityV2}, - runningExtrema: {value: 'runningExtrema', label: 'Running extrema notification', component: DemoRunningExtrema} + speechHighlight: { value: 'speechHighlight', label: 'Speak points in range', component: DemoSpeakRange }, + fileOutput: { value: 'fileOutput', label: 'Point of interest notification', component: DemoFileOutput }, + slopeParityV1: { + value: 'slopeParityV1', + label: 'Slope direction change notification', + component: DemoSlopeParityV1, + }, + slopeParityV2: { value: 'slopeParityV2', label: 'Slope parity notification', component: DemoSlopeParityV2 }, + slope: { value: 'slope', label: 'Slope display', component: DemoSlope }, + runningExtrema: { value: 'runningExtrema', label: 'Running extrema notification', component: DemoRunningExtrema }, } let demoViewRef: React.RefObject | DemoHighlightRegion> = React.createRef() diff --git a/src/sonification/handler/DataHandler.ts b/src/sonification/handler/DataHandler.ts index 1a46da7..bc821b6 100644 --- a/src/sonification/handler/DataHandler.ts +++ b/src/sonification/handler/DataHandler.ts @@ -2,26 +2,15 @@ import { DatumOutput } from '../output/DatumOutput' import { filter, Observable, Subject, tap } from 'rxjs' import { getSonificationLoggingLevel, OutputStateChange, SonificationLoggingLevel } from '../OutputConstants' - const DEBUG = false - /** * A DataHandler class is used to decide how to output each data point. */ export abstract class DataHandler extends Subject { - - - - - /** - * Add an output and make sure it has the right subscriptions - * - * @param output The output to add - */ - public addOutput(output: DatumOutput) { - this.setupOutputSubscription(output) - } + // TODO: right now has to be provided at construction time. Change? + private output: DatumOutput | undefined + private outputSubscription /** * Set up a subscription so we are notified about events @@ -30,8 +19,9 @@ export abstract class DataHandler extends Subject { * @param sink The sink that is producing data for us */ public setupSubscription(sink$: Observable) { - debugStatic (SonificationLoggingLevel.DEBUG,"setting up subscription for sink") + debugStatic(SonificationLoggingLevel.DEBUG, 'setting up subscription for sink') sink$.pipe(debug(SonificationLoggingLevel.DEBUG, 'DataHandler', DEBUG)).subscribe(this) + if (this.output) this.setupOutputSubscription(this.output, sink$) } /** @@ -39,30 +29,27 @@ export abstract class DataHandler extends Subject { * * @param output The output object */ - setupOutputSubscription(output: DatumOutput) { - let outputStream$ = this.pipe(filter((val) => val != undefined)) + setupOutputSubscription(output: DatumOutput, stream$: Observable) { + let outputStream$ = stream$.pipe( + filter((val) => val != undefined), + debug(SonificationLoggingLevel.DEBUG, `Output val`, true), + ) debugStatic(SonificationLoggingLevel.DEBUG, 'setting up output') - output.setupSubscription(outputStream$ as Observable) - + this.outputSubscription = output.setupSubscription(outputStream$ as Observable) } public toString(): string { return `DataHandler ${this}` -} - - + } /** * @param output An optional way to output the data */ constructor(output?: DatumOutput) { super() - if (output) this.addOutput(output) - + this.output = output } } - - //////////// DEBUGGING ////////////////// import { tag } from 'rxjs-spy/operators/tag' import { Datum } from '../Datum' diff --git a/src/sonification/handler/ScaleHandler.ts b/src/sonification/handler/ScaleHandler.ts index 41c2aa4..900ff28 100644 --- a/src/sonification/handler/ScaleHandler.ts +++ b/src/sonification/handler/ScaleHandler.ts @@ -12,11 +12,8 @@ import { import { RangeEndExpander } from '../stat/RangeEndExpander' import assert from 'assert' - const DEBUG = false - - /** * A DataHandler that scales the given value based on a specified min and max * @@ -118,7 +115,6 @@ export class ScaleHandler extends DataHandler { }), debug(SonificationLoggingLevel.DEBUG, 'scaled', DEBUG), - ), ) } @@ -148,11 +144,9 @@ const debug = (level: number, message: string, watch: boolean) => (source: Obser } const debugStatic = (level: number, message: string) => { - if (DEBUG) { if (level >= getSonificationLoggingLevel()) { console.log(message) } //else console.log('debug message dumped') } - } diff --git a/src/sonification/handler/SlopeHandler.ts b/src/sonification/handler/SlopeHandler.ts new file mode 100644 index 0000000..0087ce3 --- /dev/null +++ b/src/sonification/handler/SlopeHandler.ts @@ -0,0 +1,89 @@ +import { filter, map, Observable, tap, withLatestFrom } from 'rxjs' +import { DatumOutput } from '../output/DatumOutput' +import { getSonificationLoggingLevel, OutputStateChange, SonificationLoggingLevel } from '../OutputConstants' +import { DataHandler } from './DataHandler' +import { Slope } from '../stat/Slope' + +const DEBUG = true + +/** + * A DataHandler that tracks the slope of the data + * @todo change this to take a function that decides how to filter? + */ +export class SlopeHandler extends DataHandler { + /** + * Constructor + * + * @param sink. DataSink that is providing data to this Handler. + * @param output. Optional output for this data + * @param direction. -1 for decreasing, 1 for increasing. Defaults to 0 if not provided. + */ + constructor(output?: DatumOutput) { + super(output) + } + + /** + * Set up a subscription so we are notified about events + * Override this if the data needs to be modified in some way + * + * @param sink The sink that is producing data for us + */ + public setupSubscription(sink$: Observable) { + debugStatic(SonificationLoggingLevel.DEBUG, `setting up subscription for ${this} ${sink$}`) + let slope$ = new SlopeChange(sink$) + + super.setupSubscription( + sink$.pipe( + debug(SonificationLoggingLevel.DEBUG, 'slopeOutput val', true), + withLatestFrom(slope$), + map((vals) => { + debugStatic(SonificationLoggingLevel.DEBUG, `vals ${vals} ${vals[1]}: vals`) + let datum = vals[0] + try { + datum.value = vals[1] + } catch (e: unknown) { + datum = vals[0] + } + return datum + }), + debug(SonificationLoggingLevel.DEBUG, 'slopeOutput val', true), + ), + ) + } + + /** + * @returns A string describing this class including its range. + */ + public toString(): string { + return `SlopeParityHandler` + } +} + +//////////// DEBUGGING ////////////////// +import { tag } from 'rxjs-spy/operators/tag' +import { Datum } from '../Datum' +import { SlopeChange } from '../stat/SlopeChange' +const debug = (level: number, message: string, watch: boolean) => (source: Observable) => { + if (watch) { + return source.pipe( + tap((val) => { + debugStatic(level, message + ': ' + val) + }), + tag(message), + ) + } else { + return source.pipe( + tap((val) => { + debugStatic(level, message + ': ' + val) + }), + ) + } +} + +const debugStatic = (level: number, message: string) => { + if (DEBUG) { + if (level >= getSonificationLoggingLevel()) { + console.log(message) + } else console.log('debug message dumped') + } +} diff --git a/src/sonification/handler/SlopeParityHandler.ts b/src/sonification/handler/SlopeParityHandler.ts index 062cc81..d8bb34b 100644 --- a/src/sonification/handler/SlopeParityHandler.ts +++ b/src/sonification/handler/SlopeParityHandler.ts @@ -2,6 +2,7 @@ import { filter, map, Observable, tap, withLatestFrom } from 'rxjs' import { DatumOutput } from '../output/DatumOutput' import { getSonificationLoggingLevel, OutputStateChange, SonificationLoggingLevel } from '../OutputConstants' import { DataHandler } from './DataHandler' +import { SlopeChange } from '../stat/SlopeChange' const DEBUG = true @@ -42,7 +43,7 @@ export class SlopeParityHandler extends DataHandler { */ public setupSubscription(sink$: Observable) { debugStatic(SonificationLoggingLevel.DEBUG, `setting up subscription for ${this} ${sink$}`) - let slope$ = new SlopeChange(3, sink$) + let slope$ = new SlopeChange(sink$) super.setupSubscription( sink$.pipe( @@ -64,7 +65,7 @@ export class SlopeParityHandler extends DataHandler { //////////// DEBUGGING ////////////////// import { tag } from 'rxjs-spy/operators/tag' import { Datum } from '../Datum' -import { SlopeChange } from '../stat/SlopeChange' + const debug = (level: number, message: string, watch: boolean) => (source: Observable) => { if (watch) { return source.pipe( diff --git a/src/sonification/output/NoteSonify.ts b/src/sonification/output/NoteSonify.ts index e80d3ef..c81e036 100644 --- a/src/sonification/output/NoteSonify.ts +++ b/src/sonification/output/NoteSonify.ts @@ -94,7 +94,7 @@ const debug = (level: number, message: string, watch: boolean) => (source: Obser const debugStatic = (level: number, message: string) => { if (DEBUG) { if (level >= getSonificationLoggingLevel()) { - console.log(message) + console.log('slope' + message) } //else console.log('debug message dumped') } } diff --git a/src/sonification/stat/Slope.ts b/src/sonification/stat/Slope.ts new file mode 100644 index 0000000..51fa339 --- /dev/null +++ b/src/sonification/stat/Slope.ts @@ -0,0 +1,73 @@ +import { buffer, bufferCount, map, Subscription } from 'rxjs' +import { Datum } from '../Datum' +import { getSonificationLoggingLevel, OutputStateChange, SonificationLoggingLevel } from '../OutputConstants' +import { Statistic } from './Statistic' + +const DEBUG = false // true + +/** + * Returns 0 if the slopes have the same parity + * -1 if the slopes switched from positive to negative + * 1 if the slopes switched from negative to positive + */ +export class Slope extends Statistic { + /** + * What buffer should the slope average be calculated over? + */ + private slopeWindow = 3 + + /** + * + * @param stream$ The stream of data over which to calculate the statistic + * @param len The number of data points to calculate the running average over + */ + constructor(stream$: Observable) { + super(0, stream$) + } + + protected setupSubscription(stream$: Observable): Subscription { + if (!this.slopeWindow) this.slopeWindow = 3 + // TODO: figure out why typescript thinks slopeWindow is undefined + // TODO: and consider how to make window length possible to change without editing the cod + + return super.setupSubscription( + stream$.pipe( + bufferCount(this.slopeWindow, 1), + map((nums) => { + const max = Math.max(...nums) + const min = Math.min(...nums) + return max - min + }), + ), + ) + } +} + +//////////// DEBUGGING ////////////////// +import { tag } from 'rxjs-spy/operators/tag' +import { Observable, tap } from 'rxjs' + +const debug = (level: number, message: string, watch: boolean) => (source: Observable) => { + if (watch) { + return source.pipe( + tap((val) => { + debugStatic(level, message + ': ' + val) + }), + tag(message), + ) + } else { + return source.pipe( + tap((val) => { + debugStatic(level, message + ': ' + val) + }), + ) + } +} + +const debugStatic = (level: number, message: string) => { + if (DEBUG) { + if (level >= getSonificationLoggingLevel()) { + console.log(message) + } else console.log('debug message dumped') + } +} diff --git a/src/sonification/stat/SlopeChange.ts b/src/sonification/stat/SlopeChange.ts index 28a46af..de95ced 100644 --- a/src/sonification/stat/SlopeChange.ts +++ b/src/sonification/stat/SlopeChange.ts @@ -1,55 +1,81 @@ -import { bufferCount, combineLatest, map, mergeMap, Observable, reduce, Subscription, take, windowCount } from 'rxjs' +import { buffer, bufferCount, map, Subscription } from 'rxjs' import { Datum } from '../Datum' -import { OutputStateChange } from '../OutputConstants' +import { getSonificationLoggingLevel, OutputStateChange, SonificationLoggingLevel } from '../OutputConstants' import { Statistic } from './Statistic' +const DEBUG = false // true + /** - * Calculates a running average based on the last n values seen. + * Returns 0 if the slopes have the same parity + * -1 if the slopes switched from positive to negative + * 1 if the slopes switched from negative to positive */ export class SlopeChange extends Statistic { /** * What buffer should the slope average be calculated over? */ - buffer = 3 + private slopeWindow = 3 /** * * @param stream$ The stream of data over which to calculate the statistic * @param len The number of data points to calculate the running average over */ - constructor(len: number, stream$: Observable) { + constructor(stream$: Observable) { super(0, stream$) - this.buffer = len ? len : this.buffer } protected setupSubscription(stream$: Observable): Subscription { - let windows$ = stream$.pipe(windowCount(this.buffer)) + if (!this.slopeWindow) this.slopeWindow = 3 + // TODO: figure out why typescript thinks slopeWindow is undefined + // TODO: and consider how to make window length possible to change without editing the code return super.setupSubscription( - windows$.pipe( - bufferCount(2), - mergeMap((frames) => { - const total1$ = frames[0].pipe( - reduce((acc, curr) => { - acc += curr - return acc - }, 0), - take(1), - ) - const total2$ = frames[1].pipe( - reduce((acc, curr) => { - acc += curr - return acc - }, 0), - take(1), - ) - return combineLatest([total1$, total2$]).pipe( - map((totals) => { - const diff = 1 / (totals[0] / this.buffer) - 1 / (totals[1] / this.buffer) - return diff > 0 ? 1 : diff == 0 ? 0 : -1 - }), - ) + stream$.pipe( + bufferCount(this.slopeWindow, 1), + map((nums) => { + const max = Math.max(...nums) + const min = Math.min(...nums) + return max - min + }), + bufferCount(2, 0), + map((slopes) => { + slopes[0] = slopes[0] >= 0 ? 1 : -1 + slopes[1] = slopes[1] >= 0 ? 1 : -1 + if (slopes[0] == slopes[1]) return 0 + if (slopes[0] > slopes[1]) return -1 + return 1 }), + debug(SonificationLoggingLevel.DEBUG, 'result', DEBUG), ), ) } } + +//////////// DEBUGGING ////////////////// +import { tag } from 'rxjs-spy/operators/tag' +import { Observable, tap } from 'rxjs' + +const debug = (level: number, message: string, watch: boolean) => (source: Observable) => { + if (watch) { + return source.pipe( + tap((val) => { + debugStatic(level, message + ': ' + val) + }), + tag(message), + ) + } else { + return source.pipe( + tap((val) => { + debugStatic(level, message + ': ' + val) + }), + ) + } +} + +const debugStatic = (level: number, message: string) => { + if (DEBUG) { + if (level >= getSonificationLoggingLevel()) { + console.log(message) + } else console.log('debug message dumped') + } +} diff --git a/src/views/demos/DemoSlope.tsx b/src/views/demos/DemoSlope.tsx new file mode 100644 index 0000000..5728257 --- /dev/null +++ b/src/views/demos/DemoSlope.tsx @@ -0,0 +1,87 @@ +import React from 'react' + +import { TextField } from '@mui/material' +import { IDemoView } from './IDemoView' +import { SlopeHandler } from '../../sonification/handler/SlopeHandler' +import { FileOutput } from '../../sonification/output/FileOutput' +import { DemoSimple, DemoSimpleProps, DemoSimpleState } from './DemoSimple' +import { NoteHandler } from '../../sonification/handler/NoteHandler' +import { OutputEngine } from '../../sonification/OutputEngine' +import { Box, Button, Input } from '@mui/material' +import { NoteSonify } from '../../sonification/output/NoteSonify' + +const DEBUG = true + +export interface DemoSlopeState extends DemoSimpleState { + targetValues: number[] +} +export interface DemoSlopeProps extends DemoSimpleProps { + dataSummary: any +} + +export class DemoSlope extends DemoSimple implements IDemoView { + filter: SlopeHandler | undefined + private _inputFile: React.RefObject + private _buffer: ArrayBuffer | undefined + + constructor(props: DemoSlopeProps) { + super(props) + this.state = { + // currently just chooses max as the default + targetValues: [this.props.dataSummary.max], + } + this._inputFile = React.createRef() + } + + public render() { + const { targetValues } = this.state + + return ( +
+ +
+ ) + } + + private _handleFileChange = (event: React.FormEvent) => { + if (DEBUG) console.log('file changed!') + let target: any = event.target + if (target && target.files && target.files.length === 1) { + console.log(event) + let file: File = target.files[0] + // process file + file.arrayBuffer() + .then((buffer) => { + // if (DEBUG) console.log(buffer.byteLength) + // byte length is not 0 from console.log statements + this._buffer = buffer + if (DEBUG) console.log('buffer updated!') + }) + .catch(console.error) + } + } + + ////////// HELPER METHODS /////////////// + public initializeSink() { + console.log('setting up note sonify for slope') + this.sink = OutputEngine.getInstance().addSink('DemoSlope') + this.sink.addDataHandler(new SlopeHandler(new NoteSonify())) + if (DEBUG) console.log('sink initialized') + this.sink.addDataHandler(new NoteHandler()) + return this.sink + } +} From e26230b37ef0383e34e51becaf6f592998d51b64 Mon Sep 17 00:00:00 2001 From: Jennifer Mankoff Date: Tue, 8 Mar 2022 16:26:48 -0800 Subject: [PATCH 03/29] jen's in-progress code --- src/pages/Jacdac.tsx | 93 +++++++++---------- src/sonification/DataSink.ts | 70 ++++++++------ src/sonification/stat/RunningAverage.ts | 6 +- src/views/demos/DemoFileOutput.tsx | 45 +++++---- src/views/demos/DemoHighlightRegion.tsx | 14 +-- src/views/demos/DemoRunningExtrema.tsx | 13 +-- src/views/demos/DemoSimple.tsx | 29 +++--- src/views/demos/DemoSlope.tsx | 6 +- src/views/demos/DemoSlopeParityV1.tsx | 45 +++++---- src/views/demos/DemoSlopeParityV2.tsx | 50 +++++----- src/views/demos/DemoSpeakRange.tsx | 12 +-- .../demos/ExperimentalDemoHighlightRegion.tsx | 4 +- 12 files changed, 189 insertions(+), 198 deletions(-) diff --git a/src/pages/Jacdac.tsx b/src/pages/Jacdac.tsx index 69be79c..e7b6e7a 100644 --- a/src/pages/Jacdac.tsx +++ b/src/pages/Jacdac.tsx @@ -23,11 +23,9 @@ const TONE_THROTTLE = 100 * @returns observable */ - function filterNullish(): UnaryFunction, Observable> { - return pipe( - filter(x => x != null) as OperatorFunction - ); - } +function filterNullish(): UnaryFunction, Observable> { + return pipe(filter((x) => x != null) as OperatorFunction) +} function ConnectButton() { const bus = useBus() @@ -55,11 +53,11 @@ function ConnectButton() { REPORT_UPDATE, // don't trigger more than every 100ms throttle(async () => { - if (!streaming || !xSink || !ySink || !zSink ) return + if (!streaming || !xSink || !ySink || !zSink) return const [x, y, z] = accelService.readingRegister.unpackedValue console.log('vpotluri: calling PushPoint.') - if(xSink && xAxisStream) xAxisStream.next(new Datum(xSink.id,x)) - if(ySink && yAxisStream) yAxisStream.next(new Datum(ySink.id,y)) + if (xSink && xAxisStream) xAxisStream.next(new Datum(xSink.id, x)) + if (ySink && yAxisStream) yAxisStream.next(new Datum(ySink.id, y)) // if(zSink && zAxisStream) zAxisStream.next(new Datum(zSink.id,z)) // OutputEngine.getInstance().pushPoint(x, sink.id) }, TONE_THROTTLE), @@ -80,85 +78,78 @@ function ConnectButton() { } const handleStartStreaming = () => { - console.log("entering handel stream") - let xSinkID:number = 0; - let ySinkID:number = 1; - let zSinkID:number = 2; - let srcX = xSink; - let srcY = ySink; - let srcZ = zSink; + console.log('entering handel stream') + let xSinkID: number = 0 + let ySinkID: number = 1 + let zSinkID: number = 2 + let srcX = xSink + let srcY = ySink + let srcZ = zSink if (!streaming) { - console.log("streaming was false") + console.log('streaming was false') /** * check if a sink exists to stream X axis data to. else create one. */ if (!srcX) { - srcX = OutputEngine.getInstance().addSink('jacdac accelerometer X axis') console.log(`added sink to stream x axis data ${xSink}`) - srcX.addDataHandler(new NoteHandler([-1,1],-1)) + srcX.addDataHandler(new NoteHandler([-1, 1], -1), false) // src.addDataHandler(new FilterRangeHandler(new NoiseSonify(), [-1, 0])) // dummy stats. Do we know the min and max for accelerometer? - xSinkID = srcX.id; + xSinkID = srcX.id setXSink(srcX) } /** * check if a sink exists to stream Y axis data to. else create one. */ - if (!srcY) { + if (!srcY) { srcY = OutputEngine.getInstance().addSink('jacdac accelerometer Y axis') console.log(`added sink to stream y axis data ${ySink}`) - srcY.addDataHandler(new NoteHandler([-1,1],1)) + srcY.addDataHandler(new NoteHandler([-1, 1], 1), false) // src.addDataHandler(new FilterRangeHandler(new NoiseSonify(), [-1, 0])) // dummy stats. Do we know the min and max for accelerometer? - ySinkID = srcY.id; + ySinkID = srcY.id setYSink(srcY) } /** * check if a sink exists to stream Z axis data to. else create one. */ - if (!srcZ) { + if (!srcZ) { srcZ = OutputEngine.getInstance().addSink('jacdac accelerometer Z axis') console.log(`added sink to stream z axis data ${zSink}`) - srcZ.addDataHandler(new NoteHandler([-1,1],0.6)) + srcZ.addDataHandler(new NoteHandler([-1, 1], 0.6), false) // src.addDataHandler(new FilterRangeHandler(new NoiseSonify(), [-1, 0])) // dummy stats. Do we know the min and max for accelerometer? - zSinkID = srcZ.id; + zSinkID = srcZ.id setZSink(srcZ) } -/** - * check if a observable exists for each of the axes. - * If not, create an RXJS Subject, filter out null values and change it to be typed as observable, and then set this as a stream for the source. - */ + /** + * check if a observable exists for each of the axes. + * If not, create an RXJS Subject, filter out null values and change it to be typed as observable, and then set this as a stream for the source. + */ - let sourceX = xAxisStream; - if(!sourceX) - { - sourceX = new Subject(); - setXAxisStream(sourceX) - OutputEngine.getInstance().setStream(xSinkID,sourceX.pipe(filterNullish())) - - } - - let sourceY = yAxisStream; - if(!sourceY) - { - sourceY = new Subject(); - setYAxisStream(sourceY) - OutputEngine.getInstance().setStream(ySinkID,sourceY.pipe(filterNullish())) + let sourceX = xAxisStream + if (!sourceX) { + sourceX = new Subject() + setXAxisStream(sourceX) + OutputEngine.getInstance().setStream(xSinkID, sourceX.pipe(filterNullish())) + } + let sourceY = yAxisStream + if (!sourceY) { + sourceY = new Subject() + setYAxisStream(sourceY) + OutputEngine.getInstance().setStream(ySinkID, sourceY.pipe(filterNullish())) } - let sourceZ = zAxisStream; - if(!sourceZ) - { - sourceZ = new Subject(); + let sourceZ = zAxisStream + if (!sourceZ) { + sourceZ = new Subject() setZAxisStream(sourceZ) - OutputEngine.getInstance().setStream(zSinkID,sourceZ.pipe(filterNullish())) - - } + OutputEngine.getInstance().setStream(zSinkID, sourceZ.pipe(filterNullish())) + } OutputEngine.getInstance().next(OutputStateChange.Play) } else { diff --git a/src/sonification/DataSink.ts b/src/sonification/DataSink.ts index 35a0444..30baa95 100644 --- a/src/sonification/DataSink.ts +++ b/src/sonification/DataSink.ts @@ -2,10 +2,8 @@ import { map, Observable, tap, Subject } from 'rxjs' import { getSonificationLoggingLevel, OutputStateChange, SonificationLoggingLevel } from './OutputConstants' import { DataHandler } from './handler/DataHandler' - const DEBUG = true - /** * The DataSink for a stream of data */ @@ -24,26 +22,48 @@ export class DataSink extends Subject { //////////////////////////////// HANDLERS /////////////////////////////////// /** - * A list of DataHandlers. DataHandlers are passed each new Datum when it arrives. + * A list of DataHandler chains. DataHandlers are passed each new Datum when it arrives. + * We store only the first and last handler in the chain. */ - private _dataHandlers: Array - overrideDatum: boolean - public removeDataHandler(dataHandler: DataHandler) { - this._dataHandlers = this._dataHandlers.filter((dataHandler) => dataHandler !== dataHandler) - } - public addDataHandler(dataHandler: DataHandler) { - debugStatic(SonificationLoggingLevel.DEBUG,`Adding data handeler: ${dataHandler}. length of _handelers of sink is ${this._dataHandlers.length}`) - let observable = this as Observable + private _dataHandlers: Map, Observable]> - if(this.overrideDatum) { - if (this._dataHandlers.length > 0) - observable = this._dataHandlers[this._dataHandlers.length - 1] as Observable + /** + * Adds a new data handler. Creates a chain based on what is passed in. + * Only the last handler in the chain is stored, since that's what we "attach" to. + * + * @param dataHandler The data handler to add + * @param key The chain to add it to. If not specified, creates a new chain + * @return The key for the new chain, or the key passed in. + */ + public addDataHandler(dataHandler: DataHandler, chain: boolean, key?: number): number { + let observable, head, caboose + let maxKey = 0 + console.log('key' + key) + + if (chain) { + head = this._dataHandlers[key as number][0] + caboose = this._dataHandlers[key as number][1] + observable = caboose + } else { + // figure out whether a key was provided and is already in the map. + // if so, we will subscribe to the tail of that chain + // if not, we will create a new key larger than all previous ones (so it is unique) + // and subscribe to this data sink + for (let handlerId of this._dataHandlers.keys()) { + console.log('key' + key + ', ' + handlerId) + if (handlerId > maxKey) maxKey = handlerId + key = maxKey + 1 + } + observable = head = this } - - - debugStatic(SonificationLoggingLevel.DEBUG, `printing ${observable}`) + debugStatic(SonificationLoggingLevel.DEBUG, `printing ${observable} with key ${key}`) dataHandler.setupSubscription(observable) - this._dataHandlers.push(dataHandler) + this._dataHandlers.set(key as number, [head, dataHandler]) + debugStatic( + SonificationLoggingLevel.DEBUG, + `Added data handeler: ${dataHandler} with key ${key} to caboose ${observable}.`, + ) + return key as number } //////////////////////////////// CONSTRUCTOR /////////////////////////////////// @@ -54,15 +74,11 @@ export class DataSink extends Subject { * @param description A description for the DataSink */ - constructor(id: number, description: String,overrideDatum:boolean = false) { - + constructor(id: number, description: String) { super() this.id = id this._description = description - this._dataHandlers = new Array() - - this.overrideDatum = overrideDatum - + this._dataHandlers = new Map() } //////////////////////////////// HELPER METHODS /////////////////////////////////// @@ -79,9 +95,7 @@ export class DataSink extends Subject { return val }), - debug(SonificationLoggingLevel.DEBUG, `dataSink`, DEBUG), - ) .subscribe(this) } @@ -96,6 +110,8 @@ export class DataSink extends Subject { //////////// DEBUGGING ////////////////// import { tag } from 'rxjs-spy/operators/tag' import { Datum } from './Datum' +import { JD_ADVERTISEMENT_0_ACK_SUPPORTED } from 'jacdac-ts' +import { PrintOutlined } from '@mui/icons-material' const debug = (level: number, message: string, watch: boolean) => (source: Observable) => { if (watch) { return source.pipe(tag(message)) @@ -109,11 +125,9 @@ const debug = (level: number, message: string, watch: boolean) => (source: Obser } const debugStatic = (level: number, message: string) => { - if (DEBUG) { if (level >= getSonificationLoggingLevel()) { console.log(message) } //else console.log('debug message dumped') } - } diff --git a/src/sonification/stat/RunningAverage.ts b/src/sonification/stat/RunningAverage.ts index 0af5e7e..905e99c 100644 --- a/src/sonification/stat/RunningAverage.ts +++ b/src/sonification/stat/RunningAverage.ts @@ -26,12 +26,12 @@ export class RunningAverage extends Statistic { return super.setupSubscription( stream$.pipe( bufferCount(this.buffer), - map((frames) => { - const total = frames.reduce((acc, curr) => { + map((nums) => { + const total = nums.reduce((acc, curr) => { acc += curr return acc }, 0) - return 1 / (total / frames.length) + return 1 / (total / nums.length) }), ), ) diff --git a/src/views/demos/DemoFileOutput.tsx b/src/views/demos/DemoFileOutput.tsx index 2f56679..593181b 100644 --- a/src/views/demos/DemoFileOutput.tsx +++ b/src/views/demos/DemoFileOutput.tsx @@ -12,16 +12,13 @@ import { Box, Button, Input } from '@mui/material' const DEBUG = true export interface DemoFileOutputState extends DemoSimpleState { - targetValues : number[] + targetValues: number[] } export interface DemoFileOutputProps extends DemoSimpleProps { dataSummary: any } -export class DemoFileOutput - extends DemoSimple - implements IDemoView -{ +export class DemoFileOutput extends DemoSimple implements IDemoView { filter: NotificationHandler | undefined private _inputFile: React.RefObject private _buffer: ArrayBuffer | undefined @@ -30,7 +27,7 @@ export class DemoFileOutput super(props) this.state = { // currently just chooses max as the default - targetValues: [this.props.dataSummary.max] + targetValues: [this.props.dataSummary.max], } this._inputFile = React.createRef() } @@ -83,39 +80,41 @@ export class DemoFileOutput private _handleValueChange = (value: string) => { let values = value.split(',') - let targets : number[] = [] + let targets: number[] = [] for (let val of values) { let numb = parseFloat(val) if (!isNaN(numb)) { targets.push(numb) } } - this.setState({ targetValues: targets}) + this.setState({ targetValues: targets }) } private _handleFileChange = (event: React.FormEvent) => { - if (DEBUG) console.log("file changed!") - let target: any = event.target - if (target && target.files && target.files.length === 1) { - console.log(event) - let file: File = target.files[0] - // process file - file.arrayBuffer().then((buffer) => { - // if (DEBUG) console.log(buffer.byteLength) - // byte length is not 0 from console.log statements - this._buffer = buffer - if (DEBUG) console.log("buffer updated!") - }).catch(console.error) - } + if (DEBUG) console.log('file changed!') + let target: any = event.target + if (target && target.files && target.files.length === 1) { + console.log(event) + let file: File = target.files[0] + // process file + file.arrayBuffer() + .then((buffer) => { + // if (DEBUG) console.log(buffer.byteLength) + // byte length is not 0 from console.log statements + this._buffer = buffer + if (DEBUG) console.log('buffer updated!') + }) + .catch(console.error) + } } ////////// HELPER METHODS /////////////// public initializeSink() { this.sink = OutputEngine.getInstance().addSink('FileOutputDemo') this.filter = new NotificationHandler(new FileOutput(this._buffer), this.state.targetValues) - if (DEBUG) console.log("sink initialized") + if (DEBUG) console.log('sink initialized') //this.sink.addDataHandler(new NoteHandler()) - this.sink.addDataHandler(this.filter) + this.sink.addDataHandler(this.filter, false) return this.sink } } diff --git a/src/views/demos/DemoHighlightRegion.tsx b/src/views/demos/DemoHighlightRegion.tsx index 0109ead..01e4e46 100644 --- a/src/views/demos/DemoHighlightRegion.tsx +++ b/src/views/demos/DemoHighlightRegion.tsx @@ -97,12 +97,14 @@ export class DemoHighlightRegion /** * @todo vpotluri to understand: where is the update datum method for this being called? */ - this.filter = new FilterRangeHandler(new NoiseSonify(undefined,undefined,-1), [this.state.minValue, this.state.maxValue]) - - - this.sink.addDataHandler(this.filter) - this.sink.addDataHandler(new NoteHandler()) - + this.filter = new FilterRangeHandler(new NoiseSonify(undefined, undefined, -1), [ + this.state.minValue, + this.state.maxValue, + ]) + + this.sink.addDataHandler(this.filter, false) + this.sink.addDataHandler(new NoteHandler(), false) + return this.sink } } diff --git a/src/views/demos/DemoRunningExtrema.tsx b/src/views/demos/DemoRunningExtrema.tsx index ad944bf..44e2457 100644 --- a/src/views/demos/DemoRunningExtrema.tsx +++ b/src/views/demos/DemoRunningExtrema.tsx @@ -15,10 +15,7 @@ export interface DemoRunningExtremaProps extends DemoSimpleProps { dataSummary: any } -export class DemoRunningExtrema - extends DemoSimple - implements IDemoView -{ +export class DemoRunningExtrema extends DemoSimple implements IDemoView { minimumFilter: RunningExtremaHandler | undefined maximumFilter: RunningExtremaHandler | undefined @@ -31,10 +28,10 @@ export class DemoRunningExtrema this.sink = OutputEngine.getInstance().addSink('DemoSlopeParity') this.maximumFilter = new RunningExtremaHandler(new Speech(), 1) this.minimumFilter = new RunningExtremaHandler(new Speech(), -1) - if (DEBUG) console.log("sink initialized") - this.sink.addDataHandler(this.maximumFilter) - this.sink.addDataHandler(this.minimumFilter) - this.sink.addDataHandler(new NoteHandler()) + if (DEBUG) console.log('sink initialized') + this.sink.addDataHandler(this.maximumFilter, false) + this.sink.addDataHandler(this.minimumFilter, false) + this.sink.addDataHandler(new NoteHandler(), false) return this.sink } } diff --git a/src/views/demos/DemoSimple.tsx b/src/views/demos/DemoSimple.tsx index b6aadd8..86dc8de 100644 --- a/src/views/demos/DemoSimple.tsx +++ b/src/views/demos/DemoSimple.tsx @@ -12,10 +12,8 @@ import { } from '../../sonification/OutputConstants' import { Demo } from '../../pages/Demo' - const DEBUG = true - export interface DemoSimpleState {} export interface DemoSimpleProps { @@ -49,11 +47,11 @@ export class DemoSimple /** * Holder for the current DataSink object that starts after a delay */ - protected delaySink: DataSink | undefined - public getDelaySink() { - if(this.delaySink) return this.delaySink + protected delaySink: DataSink | undefined + public getDelaySink() { + if (this.delaySink) return this.delaySink else return this.initializeDelaySink() - } + } initializeDelaySink() { // SONIFICATION debugStatic(SonificationLoggingLevel.DEBUG, `adding sink`) @@ -102,11 +100,10 @@ export class DemoSimple //let delayID = this.delaySink ? this.delaySink.id : 1 - let dataCopy = Object.assign([],data) + let dataCopy = Object.assign([], data) let data$ = of(...data) //.slice(0, 8)) let delayData$ = of(...dataCopy) //.slice(0, 8)) - let timer$ = timer(0, 250).pipe(debug(SonificationLoggingLevel.DEBUG, 'point number')) let source$ = zip(data$, timer$, (num, time) => new Datum(id, num)).pipe( @@ -126,14 +123,17 @@ export class DemoSimple debugStatic(SonificationLoggingLevel.DEBUG, `adding Handler`) this.sink?.addDataHandler( - new NoteHandler([ - data.reduce((prev, curr) => (prev < curr ? prev : curr)), // min - data.reduce((prev, curr) => (prev > curr ? prev : curr)), - ],-1), + new NoteHandler( + [ + data.reduce((prev, curr) => (prev < curr ? prev : curr)), // min + data.reduce((prev, curr) => (prev > curr ? prev : curr)), + ], + -1, + ), + false, ) // max debugStatic(SonificationLoggingLevel.DEBUG, `success`) - /*let delayTimer$ = timer(0, 250).pipe(debug(SonificationLoggingLevel.DEBUG, 'point number')) let delaySource$ = zip(delayData$, delayTimer$, (num, time) => new Datum(delayID, num)).pipe(delay(1000)).pipe( @@ -163,7 +163,6 @@ export class DemoSimple )*/ // max - console.log('sending play') // Change State OutputEngine.getInstance().next(OutputStateChange.Play) @@ -216,11 +215,9 @@ const debug = (level: number, message: string) => (source: Observable) => }), ) const debugStatic = (level: number, message: string) => { - if (DEBUG) { if (level >= getSonificationLoggingLevel()) { console.log(message) } //else console.log('debug message dumped') } - } diff --git a/src/views/demos/DemoSlope.tsx b/src/views/demos/DemoSlope.tsx index 5728257..fe900af 100644 --- a/src/views/demos/DemoSlope.tsx +++ b/src/views/demos/DemoSlope.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { TextField } from '@mui/material' +import { fabClasses, TextField } from '@mui/material' import { IDemoView } from './IDemoView' import { SlopeHandler } from '../../sonification/handler/SlopeHandler' import { FileOutput } from '../../sonification/output/FileOutput' @@ -79,9 +79,9 @@ export class DemoSlope extends DemoSimple implem public initializeSink() { console.log('setting up note sonify for slope') this.sink = OutputEngine.getInstance().addSink('DemoSlope') - this.sink.addDataHandler(new SlopeHandler(new NoteSonify())) + this.sink.addDataHandler(new SlopeHandler(new NoteSonify()), false) if (DEBUG) console.log('sink initialized') - this.sink.addDataHandler(new NoteHandler()) + this.sink.addDataHandler(new NoteHandler(), false) return this.sink } } diff --git a/src/views/demos/DemoSlopeParityV1.tsx b/src/views/demos/DemoSlopeParityV1.tsx index 31347c1..116a2b1 100644 --- a/src/views/demos/DemoSlopeParityV1.tsx +++ b/src/views/demos/DemoSlopeParityV1.tsx @@ -12,16 +12,13 @@ import { Box, Button, Input } from '@mui/material' const DEBUG = true export interface DemoSlopeParityV1State extends DemoSimpleState { - targetValues : number[] + targetValues: number[] } export interface DemoSlopeParityV1Props extends DemoSimpleProps { dataSummary: any } -export class DemoSlopeParityV1 - extends DemoSimple - implements IDemoView -{ +export class DemoSlopeParityV1 extends DemoSimple implements IDemoView { filter: SlopeParityHandler | undefined private _inputFile: React.RefObject private _buffer: ArrayBuffer | undefined @@ -30,7 +27,7 @@ export class DemoSlopeParityV1 super(props) this.state = { // currently just chooses max as the default - targetValues: [this.props.dataSummary.max] + targetValues: [this.props.dataSummary.max], } this._inputFile = React.createRef() } @@ -60,28 +57,30 @@ export class DemoSlopeParityV1 } private _handleFileChange = (event: React.FormEvent) => { - if (DEBUG) console.log("file changed!") - let target: any = event.target - if (target && target.files && target.files.length === 1) { - console.log(event) - let file: File = target.files[0] - // process file - file.arrayBuffer().then((buffer) => { - // if (DEBUG) console.log(buffer.byteLength) - // byte length is not 0 from console.log statements - this._buffer = buffer - if (DEBUG) console.log("buffer updated!") - }).catch(console.error) - } + if (DEBUG) console.log('file changed!') + let target: any = event.target + if (target && target.files && target.files.length === 1) { + console.log(event) + let file: File = target.files[0] + // process file + file.arrayBuffer() + .then((buffer) => { + // if (DEBUG) console.log(buffer.byteLength) + // byte length is not 0 from console.log statements + this._buffer = buffer + if (DEBUG) console.log('buffer updated!') + }) + .catch(console.error) + } } ////////// HELPER METHODS /////////////// public initializeSink() { this.sink = OutputEngine.getInstance().addSink('DemoSlopeParityV1') this.filter = new SlopeParityHandler(new FileOutput(this._buffer)) - if (DEBUG) console.log("sink initialized") - this.sink.addDataHandler(new NoteHandler()) - this.sink.addDataHandler(this.filter) + if (DEBUG) console.log('sink initialized') + this.sink.addDataHandler(new NoteHandler(), false) + this.sink.addDataHandler(this.filter, false) return this.sink } -} \ No newline at end of file +} diff --git a/src/views/demos/DemoSlopeParityV2.tsx b/src/views/demos/DemoSlopeParityV2.tsx index d3c682e..075ff1c 100644 --- a/src/views/demos/DemoSlopeParityV2.tsx +++ b/src/views/demos/DemoSlopeParityV2.tsx @@ -15,10 +15,7 @@ export interface DemoSlopeParityV2Props extends DemoSimpleProps { dataSummary: any } -export class DemoSlopeParityV2 - extends DemoSimple - implements IDemoView -{ +export class DemoSlopeParityV2 extends DemoSimple implements IDemoView { increasingFilter: SlopeParityHandler | undefined decreasingFilter: SlopeParityHandler | undefined private _inputFile: React.RefObject @@ -31,7 +28,6 @@ export class DemoSlopeParityV2 } public render() { - return (