view src/app/Session.ts @ 509:041468f553e1 tip master

Merge pull request #57 from LucasThompson/fix/session-stack-max-call-stack Fix accidental recursion in PersistentStack
author Lucas Thompson <LucasThompson@users.noreply.github.com>
date Mon, 27 Nov 2017 11:04:30 +0000
parents 7ac80ad913c1
children
line wrap: on
line source
/**
 * Created by lucast on 08/06/2017.
 */
import {
  Item,
  RootAudioItem
} from './analysis-item/AnalysisItem';

export const exampleSession: SerialisedNotebook = {
  root: {
    id: '1',
    hasSharedTimeline: true,
    title: 'Drum Loop',
    description: 'Remotely hosted audio file',
    uri: 'https://piper-audio.github.io/waves-ui-piper/examples/assets/drum-loop.wav'
  },
  analyses: [
    {
      id: '2',
      hasSharedTimeline: true,
      extractorKey: 'vamp-example-plugins:amplitudefollower',
      outputId: 'amplitude',
      title: 'Amplitude',
      description: 'amplitude'
    },
    {
      id: '3',
      hasSharedTimeline: true,
      extractorKey: 'vamp-example-plugins:powerspectrum',
      outputId: 'powerspectrum',
      title: 'Simple Power Spectrum',
      description: 'powerspectrum'
    },

  ]
};

export interface SerialisedAnalysisItem extends Item {
  extractorKey: string;
  outputId: string;
}

export interface SerialisedNotebook {
  root: RootAudioItem;
  analyses: SerialisedAnalysisItem[];
}

export type ResourceRetriever = (url: string) => Promise<Blob>;

export const downloadResource: ResourceRetriever = async (url) => {
  const response = await fetch(url);
  const mimeType = response.headers.get('content-type');
  // Safari's fetch.blob implementation doesn't populate the type property
  // causing the audio player to fail due to an unsupported type.
  // Manually create a blob from an array buffer and the content type in
  // the response object
  const arrayBufferToBlob = async () => {
    const arrayBuffer = await response.arrayBuffer();
    return new Blob([arrayBuffer], {type: mimeType});
  };
  return mimeType ? arrayBufferToBlob() : response.blob();
};

export class PersistentStack<T> {
  private stack: T[];
  private history: T[][];
  private historyOffset: number;

  constructor() {
    this.stack = [];
    this.history = [[]];
    this.historyOffset = 0;
  }

  shiftMutating(): T {
    const item = this.stack[0];
    this.stack = this.stack.slice(1);
    return item;
  }

  shift(): T {
    const item = this.shiftMutating();
    this.updateHistory();
    return item;
  }

  unshiftMutating(item: T): number {
    this.stack = [item, ...this.stack];
    return this.stack.length;
  }

  unshift(item: T): number {
    const newLength = this.unshiftMutating(item);
    this.updateHistory();
    return newLength;
  }

  findIndex(predicate: (value: T,
                        index: number,
                        array: T[]) => boolean): number {
    return this.stack.findIndex(predicate);
  }

  findIndexAndUse(predicate: (value: T,
                              index: number,
                              array: T[]) => boolean,
                  use: (index: number) => void): boolean {
    const index = this.stack.findIndex(predicate);
    const didFind = index !== -1;
    if (didFind) {
      use(index);
    }
    return didFind;
  }

  filter(predicate: (value: T, index: number, array: T[]) => boolean): T[] {
    return this.stack.filter(predicate);
  }

  get(index: number): T {
    return this.stack[index];
  }

  set(index: number, value: T) {
    this.setMutating(index, value);
    this.updateHistory();
  }

  setMutating(index: number, value: T) {
    this.stack = [
      ...this.stack.slice(0, index),
      value,
      ...this.stack.slice(index + 1)
    ];
  }

  map<U>(transform: (value: T, index: number, array: T[]) => U): U[] {
    return this.stack.map(transform);
  }

  reduce<U>(reducer: (previousValue: U,
                      currentValue: T,
                      currentIndex: number,
                      array: T[]) => U,
            initialValue: U): U {
    return this.stack.reduce(reducer, initialValue);
  }

  remove(...indices: number[]) {
    this.stack = this.stack.reduce((acc, item, i) => {
      if (!indices.includes(i)) {
        acc.push(item);
      }
      return acc;
    }, [] as T[]);
    this.updateHistory();
  }

  stepBack(): void {
    const latest = this.history.length - 1;
    if (++this.historyOffset <= latest) {
      this.stack = this.history[latest - this.historyOffset];
    } else {
      this.historyOffset = latest;
    }
  }

  stepForward(): void {
    const latest = this.history.length - 1;
    if (--this.historyOffset >= 0) {
      this.stack = this.history[latest - this.historyOffset];
    } else {
      this.historyOffset = 0;
    }
  }

  toIterable(): Iterable<T> {
    return this.stack;
  }

  private updateHistory(): void {
    if (this.historyOffset !== 0) {
      this.history = this.history.slice(
        0,
        this.history.length - this.historyOffset
      );
      this.historyOffset = 0;
    }
    this.history.push([...this.stack]);
  }
}