Skip to content

Commit f33b848

Browse files
committed
Add song detune modulator. Fix behavior of "N" key to be undo-able properly, and add new patterns to the song if needed. Max volume is now in line with beepbox's. Add jummbox_offline.html.
1 parent 729759b commit f33b848

File tree

9 files changed

+413
-299
lines changed

9 files changed

+413
-299
lines changed

compile_beepbox_editor.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ uglifyjs \
1414
# Combine the html and js into a single file for the offline version
1515
sed \
1616
-e '/INSERT_BEEPBOX_SOURCE_HERE/{r website/beepbox_editor.min.js' -e 'd' -e '}' \
17-
website/beepbox_offline_template.html \
18-
> website/beepbox_offline.html
17+
website/jummbox_offline_template.html \
18+
> website/jummbox_offline.html

editor/Piano.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,9 @@ namespace beepbox {
455455
case ModSetting.mstTempo:
456456
secondRow = "Tempo";
457457
break;
458+
case ModSetting.mstSongDetune:
459+
secondRow = "Detune";
460+
break;
458461
}
459462
}
460463

editor/SongEditor.ts

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ namespace beepbox {
367367
private readonly _playButton: HTMLButtonElement = button({ style: "width: 80px;", type: "button" });
368368
private readonly _prevBarButton: HTMLButtonElement = button({ className: "prevBarButton", style: "width: 40px;", type: "button", title: "Previous Bar (left bracket)" });
369369
private readonly _nextBarButton: HTMLButtonElement = button({ className: "nextBarButton", style: "width: 40px;", type: "button", title: "Next Bar (right bracket)" });
370-
private readonly _volumeSlider: Slider = new Slider(input({ title: "main volume", style: "width: 5em; flex-grow: 1; margin: 0;", type: "range", min: "0", max: "100", value: "50", step: "1" }), this._doc, null, false);
370+
private readonly _volumeSlider: Slider = new Slider(input({ title: "main volume", style: "width: 5em; flex-grow: 1; margin: 0;", type: "range", min: "0", max: "75", value: "50", step: "1" }), this._doc, null, false);
371371
private readonly _fileMenu: HTMLSelectElement = select({ style: "width: 100%;" },
372372
option({ selected: true, disabled: true, hidden: false }, "File"), // todo: "hidden" should be true but looks wrong on mac chrome, adds checkmark next to first visible option. :(
373373
option({ value: "new" }, "+ New Blank Song"),
@@ -1601,6 +1601,7 @@ namespace beepbox {
16011601
settingList.push("tempo");
16021602
settingList.push("reverb");
16031603
settingList.push("next bar");
1604+
settingList.push("song detune");
16041605
}
16051606
// Populate mod setting options for instrument scope.
16061607
else {
@@ -1736,6 +1737,12 @@ namespace beepbox {
17361737
else
17371738
needReset = true;
17381739
break;
1740+
case ModSetting.mstSongDetune:
1741+
if (modStatus == ModStatus.msForSong)
1742+
setIndex = 5;
1743+
else
1744+
needReset = true;
1745+
break;
17391746
case ModSetting.mstNone:
17401747
default:
17411748
break;
@@ -1984,32 +1991,54 @@ namespace beepbox {
19841991
// Find lowest-index unused pattern for current channel
19851992
// Shift+n - lowest-index completely empty pattern
19861993

1994+
const group: ChangeGroup = new ChangeGroup();
1995+
19871996
if (event.shiftKey || event.ctrlKey) {
19881997
let nextEmpty: number = 0;
1989-
while (this._doc.song.channels[this._doc.channel].patterns[nextEmpty].notes.length > 0
1990-
&& nextEmpty <= this._doc.song.patternsPerChannel)
1998+
while (nextEmpty < this._doc.song.patternsPerChannel && this._doc.song.channels[this._doc.channel].patterns[nextEmpty].notes.length > 0)
19911999
nextEmpty++;
19922000

1993-
if (nextEmpty <= this._doc.song.patternsPerChannel) {
1994-
this._doc.song.channels[this._doc.channel].bars[this._doc.bar] = nextEmpty + 1;
2001+
nextEmpty++; // The next empty pattern is actually the one after the found one
19952002

1996-
this._doc.notifier.changed();
1997-
}
2003+
// Can't set anything if we're at the absolute limit.
2004+
if (nextEmpty <= Config.barCountMax) {
19982005

2006+
if (nextEmpty > this._doc.song.patternsPerChannel) {
2007+
2008+
// Add extra empty pattern, if all the rest have something in them.
2009+
group.append(new ChangePatternsPerChannel(this._doc, nextEmpty));
2010+
}
2011+
2012+
// Change pattern number to lowest-index unused
2013+
group.append(new ChangePatternNumbers(this._doc, nextEmpty, this._doc.bar, this._doc.channel, 1, 1));
2014+
2015+
2016+
}
19992017
}
20002018
else {
20012019
let nextUnused: number = 1;
20022020
while (this._doc.song.channels[this._doc.channel].bars.indexOf(nextUnused) != -1
20032021
&& nextUnused <= this._doc.song.patternsPerChannel)
20042022
nextUnused++;
2023+
2024+
// Can't set anything if we're at the absolute limit.
2025+
if (nextUnused <= Config.barCountMax) {
2026+
2027+
if (nextUnused > this._doc.song.patternsPerChannel) {
2028+
2029+
// Add extra empty pattern, if all the rest are used.
2030+
group.append(new ChangePatternsPerChannel(this._doc, nextUnused));
2031+
}
20052032

2006-
if (nextUnused <= this._doc.song.patternsPerChannel) {
2007-
this._doc.song.channels[this._doc.channel].bars[this._doc.bar] = nextUnused;
2033+
// Change pattern number to lowest-index unused
2034+
group.append(new ChangePatternNumbers(this._doc, nextUnused, this._doc.bar, this._doc.channel, 1, 1));
2035+
20082036

2009-
this._doc.notifier.changed();
20102037
}
20112038
}
20122039

2040+
this._doc.record(group);
2041+
20132042
event.preventDefault();
20142043
break;
20152044
case 77: // m
@@ -2141,6 +2170,7 @@ namespace beepbox {
21412170
const instrumentCopy: any = instrument.toJsonObject();
21422171
instrumentCopy["isDrum"] = this._doc.song.getChannelIsNoise(this._doc.channel);
21432172
window.localStorage.setItem("instrumentCopy", JSON.stringify(instrumentCopy));
2173+
this._refocusStage();
21442174
}
21452175

21462176
private _pasteInstrument(): void {
@@ -2150,6 +2180,7 @@ namespace beepbox {
21502180
if (instrumentCopy != null && instrumentCopy["isDrum"] == this._doc.song.getChannelIsNoise(this._doc.channel)) {
21512181
this._doc.record(new ChangePasteInstrument(this._doc, instrument, instrumentCopy));
21522182
}
2183+
this._refocusStage();
21532184
}
21542185

21552186
private _randomPreset(): void {

editor/changes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,9 @@ namespace beepbox {
14711471
//case "vibrato speed":
14721472
// setting = ModSetting.mstVibratoSpeed;
14731473
// break;
1474+
case "song detune":
1475+
setting = ModSetting.mstSongDetune;
1476+
break;
14741477
case "none":
14751478
default:
14761479
break;

player/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ namespace beepbox {
177177
const volumeIcon: SVGSVGElement = svg({style: "flex: 0 0 12px; margin: 0 1px; width: 12px; height: 12px;", viewBox: "0 0 12 12"},
178178
path({fill: ColorConfig.uiWidgetBackground, d: "M 1 9 L 1 3 L 4 3 L 7 0 L 7 12 L 4 9 L 1 9 M 9 3 Q 12 6 9 9 L 8 8 Q 10.5 6 8 4 L 9 3 z"}),
179179
);
180-
const volumeSlider: HTMLInputElement = input({title: "volume", type: "range", value: 75, min: 0, max: 100, step: 1, style: "width: 12vw; max-width: 100px; margin: 0 1px;"});
180+
const volumeSlider: HTMLInputElement = input({title: "volume", type: "range", value: 75, min: 0, max: 75, step: 1, style: "width: 12vw; max-width: 100px; margin: 0 1px;"});
181181

182182
const zoomIcon: SVGSVGElement = svg({width: 12, height: 12, viewBox: "0 0 12 12"},
183183
circle({cx: "5", cy: "5", r: "4.5", "stroke-width": "1", stroke: "currentColor", fill: "none"}),

synth/SynthConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ namespace beepbox {
289289
public static readonly panMax: number = Config.panCenter * 2;
290290
public static readonly detuneMin: number = -50;
291291
public static readonly detuneMax: number = 50;
292+
public static readonly songDetuneMin: number = -250;
293+
public static readonly songDetuneMax: number = 250;
292294
public static readonly chords: DictionaryArray<Chord> = toNameMap([
293295
{ name: "harmony", harmonizes: true, customInterval: false, arpeggiates: false, isCustomInterval: false, strumParts: 0 },
294296
{ name: "strum", harmonizes: true, customInterval: false, arpeggiates: false, isCustomInterval: false, strumParts: 1 },

synth/synth.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,7 +1234,8 @@ namespace beepbox {
12341234
mstDetune = 15,
12351235
mstVibratoDepth = 16,
12361236
//mstVibratoSpeed = 17,
1237-
mstMaxValue = 17,
1237+
mstSongDetune = 17,
1238+
mstMaxValue = 18,
12381239
}
12391240

12401241
export class Channel {
@@ -1289,6 +1290,7 @@ namespace beepbox {
12891290
[ModSetting.mstPulseWidth, Config.pulseWidthRange],
12901291
[ModSetting.mstDetune, Config.detuneMax - Config.detuneMin],
12911292
[ModSetting.mstVibratoDepth, 50],
1293+
[ModSetting.mstSongDetune, Config.songDetuneMax - Config.songDetuneMin],
12921294
//[ModSetting.mstVibratoSpeed, 100],
12931295
]
12941296
);
@@ -1312,6 +1314,9 @@ namespace beepbox {
13121314
case ModSetting.mstDetune:
13131315
value += Config.detuneMin;
13141316
break;
1317+
case ModSetting.mstSongDetune:
1318+
value += Config.songDetuneMin;
1319+
break;
13151320
case ModSetting.mstFilterCut:
13161321
case ModSetting.mstFilterPeak:
13171322
case ModSetting.mstSongVolume:
@@ -1356,6 +1361,9 @@ namespace beepbox {
13561361
case ModSetting.mstDetune:
13571362
value -= Config.detuneMin;
13581363
break;
1364+
case ModSetting.mstSongDetune:
1365+
value -= Config.songDetuneMin;
1366+
break;
13591367
case ModSetting.mstFilterCut:
13601368
case ModSetting.mstFilterPeak:
13611369
case ModSetting.mstSongVolume:
@@ -3395,6 +3403,7 @@ namespace beepbox {
33953403
case ModSetting.mstSongVolume:
33963404
case ModSetting.mstReverb:
33973405
case ModSetting.mstTempo:
3406+
case ModSetting.mstSongDetune:
33983407
val = (this.song as Song).modValueToReal(volumeStart, setting);
33993408
nextVal = (this.song as Song).modValueToReal(volumeEnd, setting);
34003409
if (this.modValues[setting] == null || this.modValues[setting] != val || this.nextModValues[setting] != nextVal) {
@@ -3438,7 +3447,7 @@ namespace beepbox {
34383447
return val;
34393448
}
34403449

3441-
public getModValue(setting: ModSetting, forSong: boolean, channel?: number, instrument?: number, nextVal?: boolean): number {
3450+
public getModValue(setting: ModSetting, forSong: boolean, channel?: number | null, instrument?: number | null, nextVal?: boolean): number {
34423451
if (forSong) {
34433452
if (this.modValues[setting] != null && this.nextModValues[setting] != null) {
34443453
return nextVal ? this.nextModValues[setting]! : this.modValues[setting]!;
@@ -4872,16 +4881,21 @@ namespace beepbox {
48724881
arpeggioInterval = tone.pitches[getArpeggioPitchIndex(tone.pitchCount, song.rhythm, arpeggio)] - tone.pitches[0];
48734882
}
48744883

4884+
let detuneStart: number = instrument.detune / 25;
4885+
let detuneEnd: number = instrument.detune / 25;
4886+
if (synth.isModActive(ModSetting.mstDetune, false, channel, instrumentIdx)) {
4887+
detuneStart = synth.getModValue(ModSetting.mstDetune, false, channel, instrumentIdx, false) / 25;
4888+
detuneEnd = synth.getModValue(ModSetting.mstDetune, false, channel, instrumentIdx, true) / 25;
4889+
}
4890+
4891+
if (synth.isModActive(ModSetting.mstSongDetune, true)) {
4892+
detuneStart += synth.getModValue(ModSetting.mstSongDetune, true, null, null, false) / 25;
4893+
detuneEnd += synth.getModValue(ModSetting.mstSongDetune, true, null, null, true) / 25;
4894+
}
4895+
48754896
const carrierCount: number = Config.algorithms[instrument.algorithm].carrierCount;
48764897
for (let i: number = 0; i < Config.operatorCount; i++) {
48774898

4878-
let detuneStart: number = instrument.detune / 25;
4879-
let detuneEnd: number = instrument.detune / 25;
4880-
if (synth.isModActive(ModSetting.mstDetune, false, channel, instrumentIdx)) {
4881-
detuneStart = synth.getModValue(ModSetting.mstDetune, false, channel, instrumentIdx, false) / 25;
4882-
detuneEnd = synth.getModValue(ModSetting.mstDetune, false, channel, instrumentIdx, true) / 25;
4883-
}
4884-
48854899
const associatedCarrierIndex: number = Config.algorithms[instrument.algorithm].associatedCarrier[i] - 1;
48864900
const pitch: number = tone.pitches[!chord.harmonizes ? 0 : ((i < tone.pitchCount) ? i : ((associatedCarrierIndex < tone.pitchCount) ? associatedCarrierIndex : 0))];
48874901
const freqMult = Config.operatorFrequencies[instrument.operators[i].frequency].mult;
@@ -4991,6 +5005,11 @@ namespace beepbox {
49915005
detuneEnd = synth.getModValue(ModSetting.mstDetune, false, channel, instrumentIdx, true) / 25;
49925006
}
49935007

5008+
if (synth.isModActive(ModSetting.mstSongDetune, true)) {
5009+
detuneStart += synth.getModValue(ModSetting.mstSongDetune, true, null, null, false) / 25;
5010+
detuneEnd += synth.getModValue(ModSetting.mstSongDetune, true, null, null, true) / 25;
5011+
}
5012+
49945013
let pitch: number = tone.pitches[0];
49955014

49965015
if (tone.pitchCount > 1) {

0 commit comments

Comments
 (0)