diff --git a/Makefile b/Makefile index de1be7f..ff29a21 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ all: js/application.js js/preact.min.js | js -SRC = src/main.jsx src/application.jsx src/note_input.jsx src/wave_data.jsx src/utils.js src/input.jsx +SRC = src/main.jsx src/application.jsx src/note_input.jsx src/wave_data.jsx src/utils.js \ + src/input.jsx src/duration_input.jsx js/application.js : $(SRC) esbuild --bundle --jsx-factory=preact.h --jsx-fragment=preact.Fragment --format=esm \ diff --git a/js/application.js b/js/application.js index 7a3ec10..1b43a42 100644 --- a/js/application.js +++ b/js/application.js @@ -244,6 +244,100 @@ function CheckBox(props) { }); } +// src/duration_input.jsx +function split_value2(x) { + if (x === void 0 || x === null) + return ["", 0]; + if (typeof x == "number") + return [x, 0]; + var xx = x.split(":"); + if (xx.length == 2) + return xx; + if (xx.length == 1) + return [x, 0]; + return ["", 0]; +} +function DurationToSeconds(x) { + let [time, unit] = split_value2(x); + switch (+unit) { + case 0: + return +time; + case 1: + return +time / 1e3; + case 2: + return +time / 60; + default: + return 0; + } +} +var DurationInput = class extends preact.Component { + constructor(props) { + super(props); + this._amtChange = this.amtChange.bind(this); + this._unitChange = this.unitChange.bind(this); + } + amtChange(e) { + e.preventDefault(); + let { value } = this.props; + var [time, unit] = split_value2(value); + var new_time = e.target.value.replace(/^\s+|\s+$/g, ""); + var n = Number(new_time); + if (Number.isNaN(n)) { + e.target.value = time; + return; + } + this.change(new_time, unit); + } + unitChange(e) { + e.preventDefault(); + let { value } = this.props; + var [time, unit] = split_value2(value); + var new_unit = +e.target.value; + let s = DurationToSeconds(value); + if (new_unit == unit) + return; + var new_time = 0; + switch (new_unit) { + case 0: + new_time = s; + break; + case 1: + new_time = s * 1e3; + break; + case 2: + new_time = s * 60; + break; + } + this.change(new_time, new_unit); + } + change(time, unit) { + let { onChange } = this.props; + if (onChange) { + onChange(time + ":" + unit); + } + } + render() { + var { value, disabled } = this.props; + var [amt, unit] = split_value2(value); + var options = ["Seconds", "Milliseconds", "Ticks"].map((x, ix) => { + return /* @__PURE__ */ preact.h("option", { + key: ix, + value: ix + }, x); + }); + return /* @__PURE__ */ preact.h(preact.Fragment, null, /* @__PURE__ */ preact.h("input", { + type: "text", + value: amt, + disabled, + onChange: this._amtChange + }), " ", /* @__PURE__ */ preact.h("select", { + value: unit, + disabled, + onChange: this._unitChange + }, options)); + } +}; + // src/application.jsx var C4 = 4 * 12; function nmultiply(x) { @@ -324,6 +418,28 @@ function HyperDisplay(props) { const relative = offset < 0 ? -offset + 32768 : offset; return /* @__PURE__ */ preact.h("div", null, "Relative: ", relative); } +function TimerDisplay(props) { + var { osc, time } = props; + const sr = calc_sr(osc); + const cycles = time * sr; + const size = 0; + var best_res = 0; + var best_freq = 0; + for (var res = 0; res < 8; ++res) { + var shift = 1 << calc_shift(res, size); + var f = Math.round(cycles * shift / 256); + if (f >= 65536) + break; + best_res = res; + best_freq = f; + } + [best_res, best_freq] = simplify(best_res, best_freq); + var best_shift = calc_shift(best_res, size); + return /* @__PURE__ */ preact.h(preact.Fragment, null, /* @__PURE__ */ preact.h("div", null, "Resolution: ", best_res ? best_res : "N/A"), /* @__PURE__ */ preact.h("div", null, "Frequency: ", best_freq ? best_freq : "N/A"), /* @__PURE__ */ preact.h(SampleDisplay, { + freq: best_freq, + shift: best_shift + })); +} var Application = class extends preact.Component { constructor(props) { super(props); @@ -332,6 +448,7 @@ var Application = class extends preact.Component { this._resChange = this.resChange.bind(this); this._freqChange = this.freqChange.bind(this); this._noteChange = this.noteChange.bind(this); + this._durationChange = this.durationChange.bind(this); this._tabChange = this.tabChange.bind(this); this._asmChange = this.asmChange.bind(this); this._shapeChange = this.shapeChange.bind(this); @@ -398,6 +515,9 @@ var Application = class extends preact.Component { noteChange(v) { this.setState({ note: v }); } + durationChange(v) { + this.setState({ duration: v }); + } asmChange(e) { e.preventDefault(); var v = +e.target.value; @@ -477,6 +597,19 @@ var Application = class extends preact.Component { freq: in_freq })); } + timerChildren() { + var { osc, duration } = this.state; + return /* @__PURE__ */ preact.h(preact.Fragment, null, /* @__PURE__ */ preact.h("div", null, /* @__PURE__ */ preact.h("label", null, "Oscillators"), " ", /* @__PURE__ */ preact.h(Oscillators, { + value: osc, + onChange: this._oscChange + })), /* @__PURE__ */ preact.h("div", null, /* @__PURE__ */ preact.h("label", null, "Duration"), " ", /* @__PURE__ */ preact.h(DurationInput, { + value: duration, + onChange: this._durationChange + })), /* @__PURE__ */ preact.h(TimerDisplay, { + osc, + time: DurationToSeconds(duration) + })); + } hyperChildren() { var { in_freq, note, indeterminate } = this.state; if (indeterminate) @@ -518,10 +651,13 @@ var Application = class extends preact.Component { children = this.waveChildren(); break; case 4: + children = this.timerChildren(); + break; + case 5: children = this.hyperChildren(); break; } - var options = ["Sample", "Resample", "Note", "Wave", "HyperCard Pitch"].map((o, ix) => { + var options = ["Sample", "Resample", "Note", "Wave", "Timer", "HyperCard Pitch"].map((o, ix) => { return /* @__PURE__ */ preact.h("option", { key: ix, value: ix diff --git a/src/application.jsx b/src/application.jsx index 797d708..8c1f743 100644 --- a/src/application.jsx +++ b/src/application.jsx @@ -8,6 +8,7 @@ import { WaveData } from './wave_data'; import { Oscillators, WaveSize, Resolution, Frequency, Assembler, WaveShape, CheckBox } from './input'; +import { DurationInput, DurationToSeconds } from './duration_input'; const C4 = 4*12; @@ -149,6 +150,8 @@ function HyperDisplay(props) { // 261.63 = C4 // 3072 = 12 * 256 (12 = octave) + // "The high byte of this word is a semitone value; the low byte is a fractional semitone." + const r = (freq * 261.63 )/ (26_320 * pitch); const offset = Math.round(3072 * Math.log2(r)); @@ -160,6 +163,47 @@ function HyperDisplay(props) { ); } + +function TimerDisplay(props) { + var {osc, time } = props; + + const sr = calc_sr(osc); + + const cycles = time * sr; + + + // (f * 256) / shift = cycles + // f * 256 = cycles * shift + // f = (cycles * shift) / 256 + + // should calculate min. wave size. eg, 256k sample has min. shift / 512, 32768 has min shift of / 2 + + const size = 0; // 256 + + var best_res = 0; + var best_freq = 0; + for (var res = 0; res < 8; ++res) { + var shift = 1 << calc_shift(res, size); + var f = Math.round(cycles * shift / 256); + if (f >= 0x10000) break; + best_res = res; + best_freq = f; + } + + [best_res, best_freq] = simplify(best_res, best_freq); + var best_shift = calc_shift(best_res, size); + + return ( + <> +