Skip to content

Commit

Permalink
Added new preferences: autoplay and autofollow.
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnesky committed Aug 29, 2019
1 parent 2c46625 commit a2bc3bf
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 89 deletions.
63 changes: 35 additions & 28 deletions tasks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -213,20 +213,41 @@
√ layout was messed up if browser default font size is set to large.
√ add a "clean slate"/"new song" button with a little dog-eared page icon.
√ while I'm at it, the edit menu should have a pencil icon and the prefs should have a gear or wrench.
√ rename barpattern to pattern, bareditor to patterneditor, attack to envelope...
√ tempo slider could use tooltip for BPM
√ add auto-follow and auto-play preferences. (I guess autoplay is only on computers? but defaults to true?)

- add auto-follow and auto-play preferences. (I guess autoplay is only in browser? but defaults to true?)
PHASE MODULATION:
- interface to select type of instrument. wave/phase/drums? goes above instrument number.
- allow channels to be in any order regardless of type... how do they get colored? or can only pitch channels swap types? wait maybe pitch and drums both have a phase modulation variant...
- new change to swap channel type
- modulation sliders, ratio selectors, envelop selectors. steady/decay123/attack123/rise123/in-out123?/custom?
- forward compatible instrument url
- backward compatible url parsing
- dim modulator settings when modulation is zero.
- provide some visual indication for which modulators affect what.

MAJOR REMAINING FEATURES:
- phase modulation
- instrument options: reverb,chorus,fadeout,distortion (applied to all enabled channels at once?)
- embedded player
- playlist editor
- remember recent songs
- export mp3/ogg and audio buffer
- audio worklet thread
- translation

MISC:
- I don't like how when you click the wave menu then use arrow keys to select drums the editor loses focus.
- pitch bends mixed with arpeggios don’t properly check min/max bounds
- can I improve the interaction for volume envelope dragging and pitch dragging? illustrate volume envelope dragging to make it less confusing.
- Distortion applying to some channels? new song setting: distortion, nonlinear mixing of channels... can't handle a range of volumes?
- Maybe add a fadeout option that decays carrier volume until the next note starts? Or is that redundant since you can do the same thing with envelope editing?
- Bring beepbox and openscores coding styles closer together. See html functions, Synth-vs-SongPlayer
- remember recent songs in cookies or local shared object.
- export mp3 and ogg?
- asynchronous export. worker thread?
- a way to pre-generate a javascript sound buffer.
- asynchronous export
- Make an embeddable song player and album player. (See below.)
- document synth api
- export mp3 and ogg?
- do audio worker threads work yet? Maybe feature-detect them? "AudioWorkerNode" vs "AudioWorkletNode"???

EMBEDDED PLAYER:
Expand All @@ -245,7 +266,7 @@ EMBEDDED PLAYER:
- manifests to allow installing web app as mobile app. add note in instructions. add any new files to app.yaml
- allow copying the url from the share menu, so the app version can hide the url bar.
- sharing menu uses localSharedObject to remember your settings. same as editor volume?
- should I make cards for twitter/facebook/slack/discord etc? Wait I need https for that I think...
- should I make cards for twitter/facebook/slack/discord etc?
- make sure bots ignore embed.html

PLAYLIST EDITOR:
Expand All @@ -258,44 +279,30 @@ PLAYLIST EDITOR:
- when starting at a bar where all tones are at the end of the bar, go ahead and skip the beginning of the bar, up to maybe one beat before the first tone.
- put songs in browser history before/after pasting new songs.
- offsite album/playlist player, with scrolling notes. hashbang urls, bot indexable.

NEW EDITORS:
- Rename synth.ts to BeepBoxSynth.ts. Pull out some of it into CommonSynth.ts. Rename SongEditor to BeepBoxSongEditor. OR: start using directories... but Typescript is pretty mean about directories if I recall?
- custom cover art, color schemes in playlists and embed player.

MAYBE:
- compacting and concatenating songs!
- can I improve the interaction for volume envelope dragging and pitch dragging? illustrate volume envelope dragging to make it less confusing.
- If adding new note and mouse drags at least two beats closer to start than previous max, then enter precise mode where you can have any length. If flipping to backwards or <= doc.parts, reset out of precise mode.
- make some button to bookmark the song in your browser
- snap to tone edges from other channels, to help making NES style harmonies.
- piano should still play when song is paused
- translation/localization
- Get in touch with ludum dare about linking to beepbox: http://www.ludumdare.com/compo/tools/
- get in touch with http://youcanmakevideogames.com/resources/music
- new preferences: follow playhead, play at load (on desktop)?
- multitouch?
- replace piano/drums canvas with svg
- piano should still play when song is paused
- illustrate arpeggio texture on tones with multiple notes, striped fill
- @Gasparatus asked for a BPM display.
- custom cover art, color schemes in playlists and embed player.
- on linux chrome, select drop downs use same background color as select element, it's a bit too bright when focused.
- per-pattern drop down menu for instrument selection, prepopulated with common ones.
- export fade in/out checkboxes
- derive song length from number of bars with non-zero patterns
- more “effect” keys like stutter, scratch, scrub, drag playhead, speedmult, whatever.
- detect vibrate switch
- per-pattern drop down menu for instrument selection, prepopulated with common ones.
- circle of fifths display
- allow drums to be divided into shorter parts, crucial for songs like the mario overworld
- 5 parts per beat?
- double number of beat divisions
- it would be awesome to have a couple of modes where when you drag the playhead, it overrides the tempo of the song to match the motion of your mouse. In one mode it distorts the pitch like a DJ table, in the other it plays the normal pitch like SoundSPEAR.
- Distortion applying to blue and yellow channels
- Dim the column occupied by a tone to communicate mono voice?
- rename barpattern to pattern, bareditor to patterneditor, attack to envelope...
- snap to tone edges from other channels, to help making NES style harmonies.
- new song setting: distortion, nonlinear mixing of channels...
- If mouse drags at least two beats closer to start than previous max, then enter precise mode where you can have any length. If flipping to backwards or <= doc.parts, reset out of precise mode.
- compacting and concatenating songs!
- visual feedback when a note is played
- when on the same pitch as a tone in an adjacent part, and you're really close to it, and you drag horizontally, go ahead and drag that adjacent tone instead of creating a new one.
- options for timing offset
- phoneme filter option, imitate vowels and voiced consonants with IIR filters, fall back on noise channel for S, T, CH, SH, etc.
- a facebook app to put songs in your profile
- preload some common tone shapes into the recentShape list. increment format number.
- make some button to bookmark the song in your browser
- Make a bunch of asserts, that the tones don't overlap, that the pins have the same duration as the tones, that the pins are in order, etc. And trace out the arguments to every change constructor and maybe even trace out the entire bar document data after every step.
2 changes: 1 addition & 1 deletion ts/ExportPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ namespace beepbox {
const intervalScale: number = isDrums ? Config.drumInterval : 1;

for (const bar of unrolledBars) {
const pattern: BarPattern | null = song.getPattern(channel, bar);
const pattern: Pattern | null = song.getPattern(channel, bar);

if (pattern != null) {

Expand Down
16 changes: 12 additions & 4 deletions ts/PatternEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ namespace beepbox {
//private _precisionX: number = 0;
private _dragChange: UndoableChange | null = null;
private _cursor: PatternCursor = new PatternCursor();
private _pattern: BarPattern | null = null;
private _pattern: Pattern | null = null;
private _playheadX: number = 0.0;
private _octaveOffset: number = 0;
private _renderedWidth: number = -1;
Expand All @@ -119,6 +119,7 @@ namespace beepbox {
private _renderedPartsPerBeat: number = -1;
private _renderedPitchChannelCount: number = -1;
private _renderedDrumChannelCount: number = -1;
private _followPlayheadBar: number = -1;

constructor(private _doc: SongDocument) {
for (let i: number = 0; i < 12; i++) {
Expand Down Expand Up @@ -381,18 +382,25 @@ namespace beepbox {
}

private _animatePlayhead = (timestamp: number): void => {
const playheadBar: number = Math.floor(this._doc.synth.playhead);
if (!this._doc.synth.playing || this._pattern == null || this._doc.song.getPattern(this._doc.channel, Math.floor(this._doc.synth.playhead)) != this._pattern) {
this._svgPlayhead.setAttribute("visibility", "hidden");
} else {
this._svgPlayhead.setAttribute("visibility", "visible");
const modPlayhead: number = this._doc.synth.playhead - Math.floor(this._doc.synth.playhead);
const modPlayhead: number = this._doc.synth.playhead - playheadBar;
if (Math.abs(modPlayhead - this._playheadX) > 0.1) {
this._playheadX = modPlayhead;
} else {
this._playheadX += (modPlayhead - this._playheadX) * 0.2;
}
this._svgPlayhead.setAttribute("x", "" + prettyNumber(this._playheadX * this._editorWidth - 2));
}

if (this._doc.synth.playing && this._doc.autoFollow && this._followPlayheadBar != playheadBar) {
new ChangeChannelBar(this._doc, this._doc.channel, playheadBar);
this._doc.notifier.notifyWatchers();
}
this._followPlayheadBar = playheadBar;
window.requestAnimationFrame(this._animatePlayhead);
}

Expand Down Expand Up @@ -616,7 +624,7 @@ namespace beepbox {
this._dragVolume = bendVolume;
this._dragVisible = true;

sequence.append(new ChangeVolumeBend(this._doc, this._pattern, this._cursor.curNote, bendPart, bendVolume, bendInterval));
sequence.append(new ChangeVolumeBend(this._doc, this._cursor.curNote, bendPart, bendVolume, bendInterval));
this._copyPins(this._cursor.curNote);
} else {
this._dragVolume = this._cursor.curNote.pins[this._cursor.nearPinIndex].volume;
Expand Down Expand Up @@ -821,7 +829,7 @@ namespace beepbox {
if (channel == this._doc.channel) continue;
if (this._doc.song.getChannelIsDrum(channel) != this._doc.song.getChannelIsDrum(this._doc.channel)) continue;

const pattern2: BarPattern | null = this._doc.song.getPattern(channel, this._doc.bar);
const pattern2: Pattern | null = this._doc.song.getPattern(channel, this._doc.bar);
if (pattern2 == null) continue;
for (const note of pattern2.notes) {
for (const pitch of note.pitches) {
Expand Down
10 changes: 8 additions & 2 deletions ts/SongDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ namespace beepbox {
public notifier: ChangeNotifier = new ChangeNotifier();
public channel: number = 0;
public bar: number = 0;
public autoPlay: boolean;
public autoFollow: boolean;
public showFifth: boolean;
public showLetters: boolean;
public showChannels: boolean;
Expand All @@ -61,6 +63,8 @@ namespace beepbox {
this.song = new Song(string);
this.synth = new Synth(this.song);

this.autoPlay = localStorage.getItem("autoPlay") != "false";
this.autoFollow = localStorage.getItem("autoFollow") == "true";
this.showFifth = localStorage.getItem("showFifth") == "true";
this.showLetters = localStorage.getItem("showLetters") == "true";
this.showChannels = localStorage.getItem("showChannels") == "true";
Expand Down Expand Up @@ -197,6 +201,8 @@ namespace beepbox {
}

public savePreferences(): void {
localStorage.setItem("autoPlay", this.autoPlay ? "true" : "false");
localStorage.setItem("autoFollow", this.autoFollow ? "true" : "false");
localStorage.setItem("showFifth", this.showFifth ? "true" : "false");
localStorage.setItem("showLetters", this.showLetters ? "true" : "false");
localStorage.setItem("showChannels", this.showChannels ? "true" : "false");
Expand All @@ -214,12 +220,12 @@ namespace beepbox {
return Math.min(1.0, Math.pow(this.volume / 50.0, 0.5)) * Math.pow(2.0, (this.volume - 75.0) / 25.0);
}

public getCurrentPattern(): BarPattern | null {
public getCurrentPattern(): Pattern | null {
return this.song.getPattern(this.channel, this.bar);
}

public getCurrentInstrument(): number {
const pattern: BarPattern | null = this.getCurrentPattern();
const pattern: Pattern | null = this.getCurrentPattern();
return pattern == null ? 0 : pattern.instrument;
}
}
Expand Down
46 changes: 37 additions & 9 deletions ts/SongEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ SOFTWARE.
namespace beepbox {
const {button, div, span, select, option, input, text} = html;

const isMobile: boolean = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|android|ipad|playbook|silk/i.test(navigator.userAgent);

function buildOptions(menu: HTMLSelectElement, items: ReadonlyArray<string | number>): HTMLSelectElement {
for (const item of items) {
menu.appendChild(option(item, item, false, false));
Expand Down Expand Up @@ -95,6 +97,8 @@ namespace beepbox {
]);
private readonly _optionsMenu: HTMLSelectElement = select({style: "width:100%;"}, [
option("", "Preferences", true, true),
option("autoPlay", "Auto Play On Load", false, false),
option("autoFollow", "Auto Follow Track", false, false),
option("showLetters", "Show Piano", false, false),
option("showFifth", "Highlight 'Fifth' Notes", false, false),
option("showChannels", "Show All Channels", false, false),
Expand Down Expand Up @@ -155,7 +159,7 @@ namespace beepbox {
public readonly mainLayer: HTMLDivElement = div({className: "beepboxEditor", tabIndex: "0"}, [
this._editorBox,
div({className: "editor-widget-column"}, [
div({style: "text-align: center; color: #999;"}, [text("BeepBox 2.2.1")]),
div({style: "text-align: center; color: #999;"}, [text("BeepBox 2.2.2")]),
div({className: "editor-widgets"}, [
div({className: "editor-controls"}, [
div({className: "playback-controls"}, [
Expand Down Expand Up @@ -261,6 +265,8 @@ namespace beepbox {

this._editorBox.addEventListener("mousedown", this._refocusStage);
this.mainLayer.addEventListener("keydown", this._whenKeyPressed);

if (isMobile) (<HTMLOptionElement> this._optionsMenu.children[1]).disabled = true;
}

private _openPrompt(promptName: string): void {
Expand Down Expand Up @@ -312,6 +318,8 @@ namespace beepbox {
this._trackEditor.render();

const optionCommands: ReadonlyArray<string> = [
(this._doc.autoPlay ? "✓ " : "") + "Auto Play On Load",
(this._doc.autoFollow ? "✓ " : "") + "Auto Follow Track",
(this._doc.showLetters ? "✓ " : "") + "Show Piano",
(this._doc.showFifth ? "✓ " : "") + "Highlight 'Fifth' Notes",
(this._doc.showChannels ? "✓ " : "") + "Show All Channels",
Expand All @@ -325,6 +333,7 @@ namespace beepbox {
setSelectedIndex(this._scaleDropDown, this._doc.song.scale);
setSelectedIndex(this._keyDropDown, this._doc.song.key);
this._tempoSlider.value = "" + this._doc.song.tempo;
this._tempoSlider.title = this._doc.song.getBeatsPerMinute() + " beats per minute";
this._reverbSlider.value = "" + this._doc.song.reverb;
setSelectedIndex(this._partDropDown, Config.partCounts.indexOf(this._doc.song.partsPerBeat));
if (this._doc.song.getChannelIsDrum(this._doc.channel)) {
Expand All @@ -341,7 +350,7 @@ namespace beepbox {
this._drumNames.style.display = "none";
}

const pattern: BarPattern | null = this._doc.getCurrentPattern();
const pattern: Pattern | null = this._doc.getCurrentPattern();

this._instrumentDropDownGroup.style.display = (this._doc.song.instrumentsPerChannel > 1) ? "flex" : "none";
this._instrumentDropDownGroup.style.visibility = (pattern != null) ? "visible" : "hidden";
Expand All @@ -366,7 +375,6 @@ namespace beepbox {
this._channelVolumeSlider.value = -this._doc.song.instrumentVolumes[this._doc.channel][instrument]+"";
setSelectedIndex(this._instrumentDropDown, instrument);

//currentState = this._doc.showLetters ? (this._doc.showScrollBar ? "showPianoAndScrollBar" : "showPiano") : (this._doc.showScrollBar ? "showScrollBar" : "hideAll");
this._piano.container.style.display = this._doc.showLetters ? "block" : "none";
this._octaveScrollBar.container.style.display = this._doc.showScrollBar ? "block" : "none";
this._barScrollBar.container.style.display = this._doc.song.barCount > this._doc.trackVisibleBars ? "" : "none";
Expand All @@ -379,6 +387,10 @@ namespace beepbox {
this._volumeSlider.value = String(this._doc.volume);

this._setPrompt(this._doc.prompt);

if (this._doc.autoFollow && !this._doc.synth.playing) {
this._doc.synth.snapToBar(this._doc.bar);
}
}

public updatePlayButton(): void {
Expand Down Expand Up @@ -435,10 +447,16 @@ namespace beepbox {
break;
case 219: // left brace
this._doc.synth.prevBar();
if (this._doc.autoFollow) {
new ChangeChannelBar(this._doc, this._doc.channel, Math.floor(this._doc.synth.playhead));
}
event.preventDefault();
break;
case 221: // right brace
this._doc.synth.nextBar();
if (this._doc.autoFollow) {
new ChangeChannelBar(this._doc, this._doc.channel, Math.floor(this._doc.synth.playhead));
}
event.preventDefault();
break;
case 189: // -
Expand Down Expand Up @@ -477,7 +495,11 @@ namespace beepbox {

private _pause(): void {
this._doc.synth.pause();
this._doc.synth.snapToBar();
if (this._doc.autoFollow) {
this._doc.synth.snapToBar(this._doc.bar);
} else {
this._doc.synth.snapToBar();
}
this.updatePlayButton();
}

Expand All @@ -486,7 +508,7 @@ namespace beepbox {
}

private _copy(): void {
const pattern: BarPattern | null = this._doc.getCurrentPattern();
const pattern: Pattern | null = this._doc.getCurrentPattern();
if (pattern == null) return;

const patternCopy: PatternCopy = {
Expand All @@ -500,7 +522,7 @@ namespace beepbox {
}

private _paste(): void {
const pattern: BarPattern | null = this._doc.getCurrentPattern();
const pattern: Pattern | null = this._doc.getCurrentPattern();
if (pattern == null) return;

const patternCopy: PatternCopy | null = JSON.parse(String(window.localStorage.getItem("patternCopy")));
Expand All @@ -511,7 +533,7 @@ namespace beepbox {
}

private _transpose(upward: boolean): void {
const pattern: BarPattern | null = this._doc.getCurrentPattern();
const pattern: Pattern | null = this._doc.getCurrentPattern();
if (pattern == null) return;

const continuousChange: boolean = this._doc.history.lastChangeWas(this._changeTranspose);
Expand Down Expand Up @@ -586,7 +608,7 @@ namespace beepbox {
}

private _whenSetInstrument = (): void => {
const pattern : BarPattern | null = this._doc.getCurrentPattern();
const pattern : Pattern | null = this._doc.getCurrentPattern();
if (pattern == null) return;
this._doc.history.record(new ChangePatternInstrument(this._doc, this._instrumentDropDown.selectedIndex, pattern));
}
Expand Down Expand Up @@ -623,6 +645,12 @@ namespace beepbox {

private _optionsMenuHandler = (event:Event): void => {
switch (this._optionsMenu.value) {
case "autoPlay":
this._doc.autoPlay = !this._doc.autoPlay;
break;
case "autoFollow":
this._doc.autoFollow = !this._doc.autoFollow;
break;
case "showLetters":
this._doc.showLetters = !this._doc.showLetters;
break;
Expand Down Expand Up @@ -651,7 +679,7 @@ namespace beepbox {
editor.mainLayer.focus();

// don't autoplay on mobile devices, wait for input.
if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|android|ipad|playbook|silk/i.test(navigator.userAgent) ) {
if (!isMobile && doc.autoPlay) {
function autoplay(): void {
if (!document.hidden) {
doc.synth.play();
Expand Down
Loading

0 comments on commit a2bc3bf

Please sign in to comment.