diff --git a/Makefile b/Makefile index 9557609..23b6b3a 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,9 @@ all: js/application.js js/preact.min.js | js -js/application.js : src/main.jsx src/application.jsx - esbuild --bundle --jsx-factory=preact.h --format=esm src/main.jsx --outfile=js/application.js +js/application.js : src/main.jsx src/application.jsx src/note_input.jsx src/radio_group.jsx + esbuild --bundle --jsx-factory=preact.h --jsx-fragment=preact.Fragment --format=esm \ + src/main.jsx --outfile=js/application.js js/preact.min.js : node_modules/preact/dist/preact.min.js diff --git a/css/application.css b/css/application.css index 6ce6f9c..c03ca51 100644 --- a/css/application.css +++ b/css/application.css @@ -14,4 +14,16 @@ html { } sub { font-style: italic; +} + +legend a { + cursor: pointer; + display: inline-block; + margin-left: .5em; +} +legend a:last-of-type { + margin-right: .5em; +} +legend a.selected { + color: blue; } \ No newline at end of file diff --git a/src/application.jsx b/src/application.jsx index d019218..b8b039d 100644 --- a/src/application.jsx +++ b/src/application.jsx @@ -1,11 +1,14 @@ // var h = preact.h; +import { NoteInput, NoteFrequency } from './note_input'; +import { RadioGroup } from './radio_group'; function calc_sr(osc) { // iigs is ~7.14Mhz / 8. Mirage is 8Mhz / 8 - return (28.63636*1000*1000/32) / (osc + 2); + // return (28.63636*1000*1000/32) / (osc + 2); + return (28_636_360/32) / (osc + 2); } function calc_shift(res,ws) { @@ -18,12 +21,22 @@ function log2(x) { } +var _onames = []; function Oscillators(props) { - var options = [] - for (var i = 1; i < 33; ++i) { - options.push(); + if (!_onames.length) { + for (var i = 1; i < 33; ++i) { + var x = (calc_sr(i) / 1000 ).toFixed(2) + " kHz"; + _onames.push(x) + } } + var options = _onames.map( (x, ix) => { + var i = ix + 1; + return + }); + // for (var i = 1; i < 33; ++i) { + // options.push(); + // } return ; } @@ -105,7 +118,53 @@ function SampleDisplay(props) { return rv; } +function SineWave() { + var rv = []; + for (n = 0; n < 256; ++n) { + var x = 128 + Math.round(127 * Math.sin(n * Math.PI / 128)); + var y = x.toString(16); if (y.length < 2) y = "0" + y; + rv.push( y ); + if ((n & 0x07) == 0x07) rv.push("\n"); + else rv.push(', '); + } + + return ( + +
+		{rv}
+		
+
+ ); +} +function NoteDisplay(props) { + + + var { osc, wave, note } = props; + + const sr = calc_sr(osc); + const note_frq = NoteFrequency(note); + + const f = note_frq / (sr / (1 << (8 + wave))); + + var best_res = 0; + var best_freq = 0; + for (var res = 0; res < 8; ++res) { + var tmp = f * (1 << calc_shift(res, wave)); + if (tmp >= 0x10000) break; + best_res = res; + best_freq = tmp; + } + + return ( + <> +
Resolution: {best_res}
+
Frequency: {Math.round(best_freq)}
+ + + + ); +} // oscillators generate addresses, not samples. // accumulator is 24-bit. @@ -122,8 +181,10 @@ export class Application extends preact.Component { this._waveChange = this.waveChange.bind(this); this._resChange = this.resChange.bind(this); this._freqChange = this.freqChange.bind(this); + this._noteChange = this.noteChange.bind(this); + this._tabChange = this.tabChange.bind(this); - this.state = { osc: 32, wave: 0, res: 0, freq: 512 }; + this.state = { osc: 32, wave: 0, res: 0, freq: 512, tab: 0, note: 4*12 }; } oscChange(e) { @@ -152,12 +213,22 @@ export class Application extends preact.Component { this.setState( { freq: v } ); } - form() { + tabChange(v) { + this.setState({ tab: v }); + } + + noteChange(v) { + this.setState({ note: v }); + } + + sampleChildren() { var { osc, wave, res, freq } = this.state; + var shift = calc_shift(res, wave); + return ( -
+ <>
@@ -170,36 +241,53 @@ export class Application extends preact.Component {
-
+ + + ); } - render() { + noteChildren() { - var { osc, wave, res, freq } = this.state; - - var shift = calc_shift(res, wave); + var { osc, wave, note } = this.state; return ( -
- { this.form() } - + <>
- Scan Rate: { (calc_sr(osc) / 1000 ).toFixed(2) } kHz + +
+
+ +
+
+
- -
+ + + ); + + } + + + render() { + + var { osc, wave, res, freq, tab } = this.state; + + // var shift = calc_shift(res, wave); + + var children; + switch(tab){ + case 0: children = this.sampleChildren(); break; + case 1: children = this.noteChildren(); break; + } + + + return ( + + { children } + ); } } -/* -window.addEventListener('load', function(){ - - preact.render( - , - document.getElementById('application') - ); -}); -*/ diff --git a/src/note_input.jsx b/src/note_input.jsx new file mode 100644 index 0000000..2449fc3 --- /dev/null +++ b/src/note_input.jsx @@ -0,0 +1,90 @@ + + +const _notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; + +const _base = [ 27.5, 55, 110, 220, 440, 880, ] + +function split_value(value) { + return [ value % 12, (value / 12) >> 0 ]; +} + +export function NoteName(value) { + + var [note, octave] = split_value(value); + + return _notes[note] + ' ' + octave; +} + +export function NoteFrequency(value) { + + var [note, octave] = split_value(value); + + var n = (note + 3 ) % 12; + if (n >= 3) octave -= 1; + var base = 27.5 * (2 ** octave); + + return base * 2 ** (n/12); +} + +export class NoteInput extends preact.Component { + + constructor(props) { + super(props); + + this._noteChange = this.noteChange.bind(this); + this._octaveChange = this.octaveChange.bind(this); + + } + + noteChange(e) { + e.preventDefault(); + + var [note, octave] = split_value(this.props.value); + note = +e.target.value; + this.change(note, octave); + } + + octaveChange(e) { + e.preventDefault(); + + var [note, octave] = split_value(this.props.value); + octave = +e.target.value; + this.change(note, octave); + } + + change(note, octave) { + var {onChange} = this.props; + if (onChange) { + var value = note + (12 * octave); + onChange(value); + } + } + + render() { + + var { onChange, value } = this.props; + + var notes = _notes.map( (x, ix) => { + return ; + }); + + var octaves = []; + for (var i = 0; i < 8; ++i) { + octaves.push( + + ); + } + + var [note, octave] = split_value(value); + + return ( + <> + + { ' ' } + + { ' ' } + { NoteFrequency(value).toFixed(2) } Hz + ); + } + +} diff --git a/src/radio_group.jsx b/src/radio_group.jsx new file mode 100644 index 0000000..761acdc --- /dev/null +++ b/src/radio_group.jsx @@ -0,0 +1,23 @@ + + +export function RadioGroup(props) { + + var { options, value, onClick } = props; + var legend = []; + if (options) { + + legend = options.map( (o, ix) => { + let _onClick = function(e) { e.preventDefault(); onClick(ix); } + return {o}; + }); + + } + + return ( +
+ {legend} + {props.children} +
+ ); + +}