2021-10-09 13:15:13 +00:00
|
|
|
|
|
|
|
|
|
// var h = preact.h;
|
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
import { NoteInput, NoteFrequency } from './note_input';
|
|
|
|
|
import { RadioGroup } from './radio_group';
|
2021-10-11 21:15:16 +00:00
|
|
|
|
import { WaveData } from './wave_data';
|
2021-10-09 13:15:13 +00:00
|
|
|
|
|
|
|
|
|
function calc_sr(osc) {
|
|
|
|
|
// iigs is ~7.14Mhz / 8. Mirage is 8Mhz / 8
|
2021-10-11 03:52:37 +00:00
|
|
|
|
// return (28.63636*1000*1000/32) / (osc + 2);
|
|
|
|
|
return (28_636_360/32) / (osc + 2);
|
2021-10-09 13:15:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function calc_shift(res,ws) {
|
|
|
|
|
return res + 9 - ws;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function log2(x) {
|
|
|
|
|
var y = Math.log2(x);
|
|
|
|
|
return (y >> 0) === y ? y : false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
var _onames = [];
|
2021-10-09 13:15:13 +00:00
|
|
|
|
function Oscillators(props) {
|
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
if (!_onames.length) {
|
|
|
|
|
for (var i = 1; i < 33; ++i) {
|
|
|
|
|
var x = (calc_sr(i) / 1000 ).toFixed(2) + " kHz";
|
|
|
|
|
_onames.push(x)
|
|
|
|
|
}
|
2021-10-09 13:15:13 +00:00
|
|
|
|
}
|
2021-10-11 03:52:37 +00:00
|
|
|
|
var options = _onames.map( (x, ix) => {
|
|
|
|
|
var i = ix + 1;
|
|
|
|
|
return <option value={i} key={i}>{i} – {x}</option>
|
|
|
|
|
});
|
|
|
|
|
// for (var i = 1; i < 33; ++i) {
|
|
|
|
|
// options.push(<option value={i} key={i}>{i}</option>);
|
|
|
|
|
// }
|
2021-10-09 13:15:13 +00:00
|
|
|
|
return <select value={props.value} onChange={props.onChange}>{options}</select>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function WaveSize(props) {
|
|
|
|
|
|
|
|
|
|
var options = []
|
|
|
|
|
for (var i = 8; i < 16; ++i) {
|
|
|
|
|
var ext = 1 << i;
|
|
|
|
|
var int = i - 8;
|
|
|
|
|
options.push(<option value={int} key={int}>{ext}</option>);
|
|
|
|
|
}
|
|
|
|
|
return <select value={props.value} onChange={props.onChange}>{options}</select>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Resolution(props) {
|
|
|
|
|
|
|
|
|
|
var options = []
|
|
|
|
|
for (var i = 0; i < 8; ++i) {
|
|
|
|
|
options.push(<option value={i} key={i}>{i}</option>);
|
|
|
|
|
}
|
|
|
|
|
return <select value={props.value} onChange={props.onChange}>{options}</select>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Frequency(props) {
|
|
|
|
|
|
|
|
|
|
/* number, min, max are not as strict as they ought to be */
|
|
|
|
|
return <input type="number" min="0" max="65535" value={props.value} onChange={props.onChange} />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-10-11 21:15:16 +00:00
|
|
|
|
|
|
|
|
|
function Assembler(props) {
|
|
|
|
|
|
2021-10-12 02:39:29 +00:00
|
|
|
|
var options = ["Merlin", "ORCA/M", "MPW"].map( (o, ix) => {
|
2021-10-11 21:15:16 +00:00
|
|
|
|
return <option key={ix} value={ix}>{o}</option>;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return <select {...props}>{options}</select>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function WaveShape(props) {
|
|
|
|
|
|
|
|
|
|
var options = ["Sine", "Square", "Triangle", "Sawtooth"].map( (o, ix) => {
|
|
|
|
|
return <option key={ix} value={ix}>{o}</option>;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return <select {...props}>{options}</select>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-10-09 13:15:13 +00:00
|
|
|
|
function nmultiply(x) {
|
|
|
|
|
if (x == 0) return 0;
|
|
|
|
|
if (x == 1) return <i>n</i>;
|
2021-10-11 17:02:12 +00:00
|
|
|
|
return <>{x} * <i>n</i></>;
|
2021-10-09 13:15:13 +00:00
|
|
|
|
// return paren ? <span>({x} * <i>n</i>)</span> : <span>{x} * <i>n</i></span>;
|
|
|
|
|
}
|
|
|
|
|
function SampleDisplay(props) {
|
|
|
|
|
|
|
|
|
|
var { shift, freq } = props;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var freq2 = log2(freq);
|
|
|
|
|
|
|
|
|
|
var fspan = <span title="Frequency">{freq}</span>;
|
|
|
|
|
|
|
|
|
|
var rv = [];
|
|
|
|
|
|
|
|
|
|
rv.push(
|
|
|
|
|
<div>
|
|
|
|
|
Sample<sub>n</sub> = RAM[ ({fspan} * <i>n</i>) >> {shift} ]
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
rv.push(
|
|
|
|
|
<div>
|
|
|
|
|
Sample<sub>n</sub> = RAM[ ({fspan} * <i>n</i>) / {1 << shift} ]
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (freq2) {
|
|
|
|
|
if (freq2 >= shift) {
|
|
|
|
|
rv.push(
|
|
|
|
|
<div>
|
|
|
|
|
Sample<sub>n</sub> = RAM[ { nmultiply(freq / ( 1 << shift)) } ]
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
rv.push(
|
|
|
|
|
<div>
|
|
|
|
|
Sample<sub>n</sub> = RAM[ { nmultiply(freq >> freq2) } >> {shift - freq2} ]
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
rv.push(
|
|
|
|
|
<div>
|
|
|
|
|
Sample<sub>n</sub> = RAM[ { nmultiply(freq >> freq2) } / { 1 << (shift - freq2) } ]
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
|
|
|
|
|
function NoteDisplay(props) {
|
|
|
|
|
|
|
|
|
|
|
2021-10-11 17:02:12 +00:00
|
|
|
|
var { osc, note } = props;
|
|
|
|
|
|
|
|
|
|
const wave = 0; // 256
|
2021-10-11 03:52:37 +00:00
|
|
|
|
|
|
|
|
|
const sr = calc_sr(osc);
|
|
|
|
|
const note_frq = NoteFrequency(note);
|
|
|
|
|
|
|
|
|
|
const f = note_frq / (sr / (1 << (8 + wave)));
|
2021-10-09 13:15:13 +00:00
|
|
|
|
|
2021-10-11 17:02:12 +00:00
|
|
|
|
// best_res = 7 - Math.ceil(Math.log2(f)) ?
|
|
|
|
|
// best_freq = f * (1 << calc_shift(best_res, 0)) ?
|
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
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 (
|
|
|
|
|
<>
|
2021-10-11 17:02:12 +00:00
|
|
|
|
<div>Wave Size: 256</div>
|
2021-10-11 03:52:37 +00:00
|
|
|
|
<div>Resolution: {best_res}</div>
|
|
|
|
|
<div>Frequency: {Math.round(best_freq)}</div>
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-10-09 13:15:13 +00:00
|
|
|
|
|
2021-10-11 17:02:12 +00:00
|
|
|
|
|
2021-10-09 13:15:13 +00:00
|
|
|
|
// oscillators generate addresses, not samples.
|
|
|
|
|
// accumulator is 24-bit.
|
|
|
|
|
// frequency is 16-bit.
|
|
|
|
|
// accumulator n = freq * n
|
|
|
|
|
// sample n = memory[(freq * n) >> res. shift]
|
|
|
|
|
|
|
|
|
|
export class Application extends preact.Component {
|
|
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
|
|
this._oscChange = this.oscChange.bind(this);
|
|
|
|
|
this._waveChange = this.waveChange.bind(this);
|
|
|
|
|
this._resChange = this.resChange.bind(this);
|
|
|
|
|
this._freqChange = this.freqChange.bind(this);
|
2021-10-11 03:52:37 +00:00
|
|
|
|
this._noteChange = this.noteChange.bind(this);
|
|
|
|
|
this._tabChange = this.tabChange.bind(this);
|
2021-10-11 21:15:16 +00:00
|
|
|
|
this._asmChange = this.asmChange.bind(this);
|
|
|
|
|
this._shapeChange = this.shapeChange.bind(this);
|
2021-10-09 13:15:13 +00:00
|
|
|
|
|
2021-10-11 21:15:16 +00:00
|
|
|
|
this.state = { osc: 32, wave: 0, res: 0, freq: 512, tab: 0, note: 4*12, assembler: 0, shape: 0 };
|
2021-10-09 13:15:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
oscChange(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
var v = +e.target.value || 0;
|
|
|
|
|
this.setState( { osc: v } );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
waveChange(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
var v = +e.target.value || 0;
|
|
|
|
|
this.setState( { wave: v } );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resChange(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
var v = +e.target.value || 0;
|
|
|
|
|
this.setState( { res: v } );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
freqChange(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
var v = +e.target.value >> 0;
|
|
|
|
|
if (v < 0) v = 0;
|
|
|
|
|
if (v > 65535) v = 65535;
|
|
|
|
|
this.setState( { freq: v } );
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-11 17:02:12 +00:00
|
|
|
|
tabChange(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
var v = +e.target.value;
|
2021-10-11 03:52:37 +00:00
|
|
|
|
this.setState({ tab: v });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
noteChange(v) {
|
|
|
|
|
this.setState({ note: v });
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-11 21:15:16 +00:00
|
|
|
|
asmChange(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
var v = +e.target.value;
|
|
|
|
|
this.setState({ assembler: v});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shapeChange(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
var v = +e.target.value;
|
|
|
|
|
this.setState({ shape: v});
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
sampleChildren() {
|
2021-10-09 13:15:13 +00:00
|
|
|
|
|
|
|
|
|
var { osc, wave, res, freq } = this.state;
|
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
var shift = calc_shift(res, wave);
|
|
|
|
|
|
2021-10-09 13:15:13 +00:00
|
|
|
|
return (
|
2021-10-11 03:52:37 +00:00
|
|
|
|
<>
|
2021-10-09 13:15:13 +00:00
|
|
|
|
<div>
|
|
|
|
|
<label>Oscillators</label> <Oscillators value={osc} onChange={this._oscChange} />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label>Wave Size</label> <WaveSize value={wave} onChange={this._waveChange} />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label>Resolution</label> <Resolution value={res} onChange={this._resChange} />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label>Frequency</label> <Frequency value={freq} onChange={this._freqChange} />
|
|
|
|
|
</div>
|
2021-10-11 03:52:37 +00:00
|
|
|
|
|
|
|
|
|
<SampleDisplay freq={freq} shift={shift} />
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
noteChildren() {
|
|
|
|
|
|
|
|
|
|
var { osc, wave, note } = this.state;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div>
|
|
|
|
|
<label>Oscillators</label> <Oscillators value={osc} onChange={this._oscChange} />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label>Note</label> <NoteInput value={note} onChange={this._noteChange} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<NoteDisplay osc={osc} note={note} wave={wave} />
|
|
|
|
|
</>
|
2021-10-09 13:15:13 +00:00
|
|
|
|
);
|
2021-10-11 03:52:37 +00:00
|
|
|
|
|
2021-10-09 13:15:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-11 21:15:16 +00:00
|
|
|
|
waveChildren() {
|
|
|
|
|
|
|
|
|
|
var { assembler, shape } = this.state;
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div>
|
|
|
|
|
<label>Assembler</label> <Assembler value={assembler} onChange={this._asmChange} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label>Wave Type</label> <WaveShape value={shape} onChange={this._shapeChange} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<WaveData assembler={assembler} shape={shape} />
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
|
2021-10-09 13:15:13 +00:00
|
|
|
|
render() {
|
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
var { osc, wave, res, freq, tab } = this.state;
|
2021-10-09 13:15:13 +00:00
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
// var shift = calc_shift(res, wave);
|
2021-10-09 13:15:13 +00:00
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
var children;
|
|
|
|
|
switch(tab){
|
|
|
|
|
case 0: children = this.sampleChildren(); break;
|
|
|
|
|
case 1: children = this.noteChildren(); break;
|
2021-10-11 21:15:16 +00:00
|
|
|
|
case 2: children = this.waveChildren(); break;
|
2021-10-11 03:52:37 +00:00
|
|
|
|
}
|
2021-10-09 13:15:13 +00:00
|
|
|
|
|
2021-10-11 21:15:16 +00:00
|
|
|
|
var options = ["Sample", "Note", "Wave"].map( (o, ix) => {
|
2021-10-11 17:02:12 +00:00
|
|
|
|
return <option key={ix} value={ix}>{o}</option>;
|
|
|
|
|
});
|
2021-10-09 13:15:13 +00:00
|
|
|
|
|
2021-10-11 03:52:37 +00:00
|
|
|
|
return (
|
2021-10-11 17:02:12 +00:00
|
|
|
|
<fieldset>
|
|
|
|
|
<legend><select value={tab} onChange={this._tabChange}>{options}</select></legend>
|
|
|
|
|
{children}
|
|
|
|
|
</fieldset>
|
2021-10-09 13:15:13 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|