├── .gitignore ├── LICENSE ├── README.md ├── fft.bqn ├── fftw.bqn ├── filter.bqn ├── lib.c ├── load.bqn ├── makefile ├── mix.bqn ├── options.bqn ├── oscillator.bqn ├── panap.bqn ├── reverb.bqn ├── scale.bqn ├── soxresample.bqn ├── tracker.bqn └── wav.bqn /.gitignore: -------------------------------------------------------------------------------- 1 | lib.so 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Marshall Lochbaum 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 7 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 8 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 9 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 10 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 11 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 12 | PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BQNoise 2 | 3 | BQN scripts for audio synthesis and processing. 4 | 5 | - mix.bqn: General mixing utilities, e.g. add, repeat, clip, reverb 6 | - panap.bqn: Fancy mono-compatible panning 7 | - oscillator.bqn: Typical oscillators and noise generators for synthesis 8 | - filter.bqn: IIR filters to adjust tonality 9 | - scale.bqn: Utilities for working with scales (in the normal Western system, 12-TET) 10 | - tracker.bqn: Sequencer to make drum tracks or other arrangements from a text template 11 | - wav.bqn: Read and write .wav files 12 | - soxresample.bqn: Bind SoX's resampler for converting .wav files; only thing I'm not brave enough to do in BQN 13 | 14 | See load.bqn and options.bqn for loading multiple scripts and options management. 15 | 16 | The following C libraries are used in CBQN if available: 17 | 18 | - `./lib.c` (build with `$ make`), for filters 19 | - [FFTW](https://en.wikipedia.org/wiki/FFTW)'s `/usr/lib/libfftw3.so.3`, for reverb 20 | - [SoX](https://en.wikipedia.org/wiki/SoX)'s `/usr/lib/libsoxr.so`, for resampling 21 | 22 | The filtering and reverb functions are also implemented in BQN, so that the only change is faster execution, by a factor of about 10 in each case. There's no BQN resampling implementation, so that SoX is needed to work with files at different frequencies. 23 | -------------------------------------------------------------------------------- /fft.bqn: -------------------------------------------------------------------------------- 1 | # Fast Fourier transform 2 | # Argument has shape ⟨l⟩ for real values or ⟨2,l⟩ for complex, l←2⋆n 3 | # Result is complex encoded as shape ⟨2,l⟩. 4 | 5 | Sin‿Cos ← •math 6 | 7 | { 8 | 𝕨𝕊⁼𝕩: (-𝕨1⊘≢¯1)𝕊𝕩 ; 9 | 10 | inv ← 𝕨≡¯1 11 | "FFT argument must be a list or have two list cells" ! (3⌊=)◶⟨0,1,2=≠,0⟩𝕩 12 | n ← 2 ⋆⁼ l ← ¯1⊑≢𝕩 13 | "FFT length must be a power of two" ! ⌊⊸=n 14 | s ← 2 ⥊˜ n 15 | 16 | r ← (Cos⋈Sin) π × (1↓s) ⥊ -⍟inv ↕⊸÷ l÷2 # Roots of unity 17 | M ← -´∘× ⋈ +´∘×⟜⌽ # Complex multiplication 18 | F ← { (r 0‿0⊸⊏⎉(n-𝕨)¨↩) ⊢ (+˝¨(𝕨⍉≍)¨r M-˝¨)𝕩 } # FFT loop 19 | ÷⟜l⍟inv >⥊¨ (↕n) F´˜ s⊸⥊¨ (1==)◶⟨<˘,⊢⋈0¨⟩ 𝕩 20 | } 21 | -------------------------------------------------------------------------------- /fftw.bqn: -------------------------------------------------------------------------------- 1 | # FFTW-based replacement for fft.bqn (unused; see reverb.bqn) 2 | 3 | plan ← "*:i32" 4 | 5 | Fn ← "/usr/lib/libfftw3.so.3"⊸•FFI 6 | createPlan ← Fn plan‿"fftw_plan_dft_1d"‿"i32"‿"*f64"‿"&f64"‿"i32"‿"i32" 7 | destroyPlan ← Fn ""‿"fftw_destroy_plan"‿plan 8 | executePlan ← Fn ""‿"fftw_execute"‿plan 9 | 10 | { 11 | 𝕨𝕊⁼𝕩: (-𝕨1⊘≢¯1)𝕊𝕩 ; 12 | 13 | "FFT argument must be a list or have two list cells" ! (3⌊=)◶⟨0,1,2=≠,0⟩𝕩 14 | Enc ← ⍉⌽ 15 | 𝕩 ↩ (2↑≍)⍟(2>=) 𝕩 16 | dir ← -⟜¬ 𝕨≡¯1 17 | sh ← 1⊑≢𝕩 18 | in ← ⥊ Enc 𝕩 19 | plan‿out ← CreatePlan ⟨sh,in,0¨in,dir,2⋆6⟩ 20 | ExecutePlan ⟨plan⟩ 21 | DestroyPlan ⟨plan⟩ 22 | ÷⟜sh⍟(1=dir) Enc⁼ sh‿2 ⥊ out 23 | } 24 | -------------------------------------------------------------------------------- /filter.bqn: -------------------------------------------------------------------------------- 1 | # IIR filters 2 | # Filters are used to make some frequencies louder or quieter in order 3 | # to change a sound's tone. 4 | # Each one takes some parameters for its left argument and a signal on 5 | # the right. 6 | 7 | ⟨ 8 | # 1-pole filters (f:frequency) 9 | Lp1, Hp1 # f low/high-pass 10 | 11 | # 2-pole filters (g:gain, w:width, Q decreases with width) 12 | Lp2, Hp2 # f Butterworth low/high-pass 13 | Lp2q,Hp2q,Bp2q # f Q Butterworth low/high/band-pass with resonance 14 | Peak # g f Q 15 | LShelf, HShelf # g f Q? low/high-shelf 16 | Notch # f w 17 | AllPass # a‿b a+bi is complex with magnitude <1 18 | 19 | # Utilities 20 | NtoQ, QtoN # Bandwidth ←→ Q for peak filters 21 | 22 | # Use Lp2⍟2 and Hp2⍟2 (4-pole Linkwitz-Riley) for crossovers 23 | 24 | # Other 2-pole low/high-pass filters (I never use these) 25 | # Filter2 generates Lp2 and Hp2 filters except the resonant ones 26 | # If the second argument is n then the filter should be called n 27 | # times and then gives a (2×n)-pass filter 28 | Filter2 29 | # Critically damped and Bessel filters 30 | Lp2_CD,Hp2_CD, Lp2_BS,Hp2_BS 31 | ⟩⇐ 32 | 33 | "filter.bqn takes a single option namespace, or no arguments" ! 1≥≠•args 34 | o ← ≠◶⟨•Import∘"options.bqn", ⊑⟩ •args 35 | Sin‿Cos‿Tan ← •math 36 | 37 | # Generalized filtering 38 | # 𝕩 is the signal to filter 39 | # 𝕨 is ⟨result coefficients , 𝕩 coefficients⟩. 40 | Filter ← { 41 | ⟨i0⟩‿⟨o0⟩ 𝕊𝕩: 42 | 0 (×⟜o0+i0⊸×)` 𝕩 43 | ;⟨i0,i1⟩‿⟨o0⟩ 𝕊𝕩: 44 | a1←a0←0 45 | 0 { (o0×𝕨)+(i1×a1↩𝕩)+i0×a0↩a1 }` 𝕩 46 | ;⟨i0,i1,i2⟩‿⟨o0,o1⟩ 𝕊𝕩: 47 | b0←a2←a1←a0←0 48 | 0 { (o1×b0↩𝕨)+(o0×b0)+(i2×a2↩𝕩)+(i1×a1↩a2)+i0×a0↩a1 }` 𝕩 49 | ;coeff 𝕊𝕩: 50 | a‿b ← 0×coeff # accumulators for input and result 51 | c ← ∾coeff 52 | { 53 | a«˜↩𝕩 54 | r←+´c×a∾b 55 | b«˜↩r 56 | r 57 | }¨ 𝕩 58 | }⎉1 59 | {𝕤 60 | fi ← "lib.so" (•state•BQN"•FFI"){𝔽} "&"‿"filter"∾∾("u64"⋈∾⟜"f64")¨"&**" 61 | Filter ↩ Fi∘{ ∾≠⊸⋈¨ ⟨𝕩⟩∾𝕨 }⎉1 62 | }⎊⊢@ 63 | _f ← { !∘0⊘(𝔽⊸Filter) } 64 | 65 | # Compute the frequency response from coefficients 66 | #Response ← ⋈⟜(1∾-)○⌽´ ⊸ ((|·÷´{+⟜(𝕩⊸×)´𝕨}¨)⎉∞‿0) ⟜ (⋆0i1×Om) 67 | 68 | Om ← (2×π)×{𝕩÷o.freq} 69 | Tom ← Tan Om÷2˙ 70 | 71 | # 1-pole low-pass and high-pass filters 72 | Lp1 ← (⋈¨ ·⋈⟜¬ 1+⌾÷Om) _f 73 | Hp1 ← ((-⊸⋈ ⋈ ⋈) 1÷∘+Om) _f 74 | 75 | # 2-pole *-pass 76 | f2types ← "bw"‿"cd"‿"bessel" 77 | Filter2 ← { type‿nPasses‿isHighpass: 78 | t ← f2types⊸⊐⌾< type 79 | ! 3 > t 80 | c ← t◶⟨4√-⟜1 , √1-˜√ , √3×0.5-˜·√-⟜0.75⟩ nPasses√2 # cutoff frequency correction 81 | x ← t ⊑ ⟨√2‿1 , 2‿1 , 3‿3⟩ # p and g 82 | Clp2 ← { 83 | e ← ¯1 ⊑ a ← 𝕨 × ≍⟜(ט) Tom 𝕩 84 | b ← (1‿2‿1 ∾˜ 2×1-˜÷e) × e ÷ 1++´a 85 | ¯3 (↑⋈↓) (1-+´)⊸∾ b 86 | } 87 | (isHighpass ⊑ ⟨ 88 | x Clp2 ÷⟜c 89 | ⟨1‿¯1‿1, 1‿¯1⟩ × x Clp2 ·{(o.freq÷2)-𝕩}×⟜c 90 | ⟩) _f 91 | } 92 | 93 | ⟨Lp2‿Hp2, Lp2_CD‿Hp2_CD, Lp2_BS‿Hp2_BS⟩ ← <˘f2types Filter2∘{𝕨‿1‿𝕩}⌜ ↕2 94 | 95 | # Once more, with resonance 96 | # Lp2q and Hp2q are identical to Lp2 and Hp2 when Q=÷√2 97 | Resfilt ← { 98 | Om⊸(Sin⊸÷⟜(2⊸×) (+⟜1 ÷˜ 𝕏 ⋈ -⟜1≍2⊸×) Cos∘⊣)´ _f 99 | } 100 | Lp2q‿Hp2q‿Bp2q ← Resfilt¨ ⟨ 1⊸-÷2‿1‿2˙ , 1⊸+÷2‿¯1‿2˙ , ×⟜1‿0‿¯1 ⟩ 101 | 102 | # Other 2-pole filters 103 | Peak ← { g‿f‿q: 104 | k ← Tom f 105 | ab ← 1‿4‿0‿2‿3‿0 ⊏ +⟜(k⊸×)´¨ ¯2‿0‿2 <⊸∾ {1‿𝕩‿1}¨ ⥊≍⟜- q ÷˜ 10⋆0⌈-⊸≍g÷20 106 | 3 (↑ ⋈ -∘↓) (1⊸↓ ÷ ⊑) ab 107 | } _f 108 | # Q to bandwidth and vice-versa for peaking filters 109 | NtoQ ← {𝕊𝕩: (√÷-⟜1)𝕩 ; 𝕊⁼𝕩: -⟜1⌾(ט)⊸+ 1+÷2×ט𝕩} 2⊸⋆ 110 | QtoN ← NtoQ⁼ 111 | 112 | Shelf ← { 113 | # t is type: 1 for bass and ¯1 for treble shelf 114 | t‿g‿f‿q ← ∾⟜(√2)⍟(3=≠) 𝕩 115 | k ← Tom f 116 | v1 ← 10 ⋆ t × 0⌈-⊸≍g÷40 117 | ab ← ⥊ 1‿0‿2⊸⊏˘ (ט1⌊v1) ÷˜ (k×v1) {+⟜(𝕨⊸×)´𝕩}⌜ ¯2‿0‿2 <⊸∾ {1‿𝕩‿1}¨ ≍⟜- q 118 | 2 (↓ ⋈○⌽ -∘↑) (1⊸↓ ÷ ⊑) ab 119 | } _f 120 | # Shortcuts for low- and high-shelf 121 | LShelf ← 1⊸∾⊸Shelf 122 | HShelf ← ¯1⊸∾⊸Shelf 123 | 124 | Notch ← { 125 | # Filter with no gain outside of the notch due to A.G. Constantinides 126 | f‿w ← 𝕩 # w is width of ≥3dB attenuation in Hz. 127 | c←2×Cos Om f ⋄ t←Tom w 128 | ⟨1,-c,1⟩‿⟨t-1,c⟩ ÷ 1+t 129 | } _f 130 | 131 | AllPass ← (⋈⟜(-∘⌽1⊸↓) ⟨1,¯2×⊑,+´×˜⟩{𝕎𝕩}¨<) _f 132 | -------------------------------------------------------------------------------- /lib.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef long long I; 4 | typedef double D; 5 | 6 | #define DO(n,stm) {I i=0,_n=(n); for(;i<_n;i++){stm}} 7 | #define DECL_ARR(t, v, l) t *v = malloc(sizeof(t)*(l)) 8 | 9 | void filter11(I,D*,D,D); 10 | void filter21(I,D*,D,D,D); 11 | void filter32(I,D*,D,D,D,D,D); 12 | void filter(I length, D* a, I lx, D* xc, I ly, D* yc) { 13 | if (lx==1 && ly==1) { return filter11(length,a,xc[0],yc[0]); } 14 | if (lx==2 && ly==1) { return filter21(length,a,xc[0],xc[1],yc[0]); } 15 | if (lx==3 && ly==2) { return filter32(length,a,xc[0],xc[1],xc[2],yc[0],yc[1]); } 16 | DECL_ARR(D, x, lx); DECL_ARR(D, y, ly); 17 | DO(lx, x[i]=0;); DO(ly, y[i]=0;); 18 | int j, ix=0, iy=0; 19 | for (j=0; j=) 24 | # Pan signal 𝕩 to position 𝕨, where 0 is hard left and 1 is hard right 25 | Pan ⇐ (25⌊∘×≍⟜¬∘⊣) ⥊⟜0⊸»˘ ((=⌜˜↕2)-·(××⌜0⊸⌈)·≍⟜-1-˜2×⊣)⊸(+˝∘×⎉¯1‿∞)⟜Stereo 26 | 27 | # Clipping functions clip to [¯1,1] by default 28 | # _norm changes this to match the format if it's an integer format 29 | _norm ⇐ { (𝕨𝔽⊢)⌾(÷⟜(2⋆1-˜¯1⊑o.fmt)) 𝕩 } 30 | # Clip signal 𝕩 to the maximum possible range. 31 | Clip ⇐ 1⌊¯1⌈⊢ 32 | # 𝕨 is an integer giving "sharpness". Perform a soft clip. 33 | Softclip ⇐ { ÷⟜(1⊸+⌾(⋆⟜(2⋆𝕨⊣3))) 𝕩 } 34 | 35 | # Multiply leading or trailing samples of 𝕩 by 𝕨. 36 | Fadefront‿Fadeback ⇐ {𝕊f: {𝕨⊸×⌾((F≠𝕨)↑⊢)𝕩}⎉1 }¨ ⊢‿- 37 | 38 | # 𝕗 is the overlap amount. Fade 𝕨 into 𝕩, linearly. 39 | _crossfade ⇐ { 40 | G←↑⋈↓ 41 | (-𝕗)⊸G⊸(∾ 1⌽∾˜○(1⊸↓) ∾⟜< (¬⊸≍I𝕗)+˝∘×≍○⊑)⟜(𝕗⊸G)⎉1 42 | } 43 | 44 | # Apply reverb impulse response (IR) 𝕨 to 𝕩. 45 | # Argument rows are extended, giving result length (𝕩+○≠𝕨)-1 (for lists). 46 | # Equivalent to (+´¨+⌜○↕○≠⊔×⌜)⎉1 except for numeric precision. 47 | reverb ⇐ •Import "reverb.bqn" 48 | 49 | # Apply stereo reverb by keeping early IR sides separate and later 50 | # blending them together; can avoid some balance issues 51 | ReverbStereoMix ⇐ { 52 | ir ← (I 100) ((¬∘⊣×≠⊸↑)⎉1 ⋈ Fadefront) 𝕨 53 | Add ir Reverb¨ ⋈⟜(+˝÷≠) 𝕩 54 | } 55 | -------------------------------------------------------------------------------- /options.bqn: -------------------------------------------------------------------------------- 1 | fBytes ⇐ •BQN"•FBytes" 2 | fmt ⇐ 1‿16 # Format: 16-bit integer 3 | freq ⇐ 44100 # Frequency: 44.1kHz 4 | Set ⇐ {fmt‿freq↩𝕩} 5 | SetFBytes ⇐ {FBytes↩𝕏} 6 | SetFreq ⇐ {freq↩𝕩} 7 | 8 | # For generating random waveforms; result is a shape 𝕩 array of uniform 9 | # random numbers between 0 and 1 10 | RandFloats ⇐ (•BQN⎊(•rand˙) "•MakeRand 1").Range⟜0 11 | 12 | # For reading/writing wave files 13 | warn_dither ⇐ 0 # Whether to warn on non-integer signal 14 | warn_clip ⇐ 1 # Whether to warn on out-of-bounds signal 15 | Dither ⇐ ⌊ (-˝ ·RandFloats 2∾≢)⊸+ 16 | Resample ⇐ { 𝕨 (resample ↩ •Import "soxresample.bqn"){𝔽} 𝕩 } 17 | -------------------------------------------------------------------------------- /oscillator.bqn: -------------------------------------------------------------------------------- 1 | # Standard oscillators for (usually subtractive) synthesis 2 | # In each case, the right argument argument is a list giving the 3 | # frequency for each sample. 4 | # If not otherwise specified, the left argument is optional and gives 5 | # the phase offset. 6 | 7 | "oscillator.bqn takes a single option namespace, or no arguments" ! 1≥≠•args 8 | o ← ≠◶⟨•Import∘"options.bqn", ⊑⟩ •args 9 | 10 | # Utilities 11 | Ce ← 1-˜2⊸× # Center: transform [0,1] to [¯1,1] 12 | Rand ← Ce {o.RandFloats 𝕩} 13 | 14 | # Basic waveforms 15 | Id ← {𝕨++`𝕩÷o.freq} # Identity 16 | P ← 1|Id # Phase 17 | 18 | # Deterministic waveforms for synthesis 19 | Saw ⇐ Ce∘P # Sawtooth wave ////// 20 | Triangle ⇐ Ce∘| {𝕨+0.25}Saw⊢ # Triangle wave ´\/\/\ 21 | Square⇐ Ce 0.5

𝕨 50 | off ← | 1-˜2×𝕨 51 | 𝕩 ↩ (-⊸≍ off × 5000 fil.Lp1 ⊏)⊸+ 𝕩 52 | a‿b ← Get_panap_coeff Shift_coeff off×25 53 | near ⌽ (b⊸AP⌾⌽ a⊸AP)⌾⊏ 𝕩 54 | } 55 | Window ← (2÷˜1+Cos π×1↓↕⊸÷6)⊸(⊣ ⌽⊸mix.Fadefront mix.Fadeback) # Tukey window 56 | PanAP ⇐ Window∘PanIR⍟(≠⟜0.5)⟜(≍˜60=↕180)⊸mix.Reverb 57 | 58 | # Empirically determined good coefficients for Panap 59 | # y is a single parameter giving the delay for low frequencies. 60 | Shift_coeff ← { 61 | f ← 732 + 79000 ÷ 1.6 ⋆˜ 10 + 𝕩 62 | rs ← 1.097‿¯2.054‿2.16 {+⟜(𝕩⊸×)´𝕨} 1e4 ÷˜ f 63 | 𝕩‿rs‿f 64 | } 65 | 66 | # Data for Shift_coeff 67 | # Values of rs which make the third derivative of the phase transfer at 68 | # zero equal to zero. (rs × 1000) is given. 69 | # frequency of max phase difference 70 | # 2000 1750 1500 1250 1100 1000 71 | # s 5 782 804 827 852 868 878 72 | # h 10 816 829 845 864 876 885 73 | # i 15 849 857 866 878 887 894 74 | # f 20 875 879 885 893 899 904 75 | # t 25 895 897 901 906 910 913 76 | # 77 | # "Good" shift, frequency, and rs values. 78 | # Frequency is determined from the formula in Shift_coeff. 79 | # In Shift_coeff, rs is modelled as quadratic w.r.t frequency. 80 | # ˜0 2716 0.698 81 | # 0.1 2685 0.701 82 | # 1 2436 0.725 83 | # 2.5 2121 0.759 84 | # 5 1769 0.802 85 | # 10 1387 0.853 86 | # 15 1190 0.882 87 | # 20 1074 0.900 88 | # 25 1000 0.913 89 | # 30 948 0.924 90 | 91 | # ========================================================= 92 | # Argument is ⟨number of samples to shift, magnitude, stop frequency⟩. 93 | # Magnitude should be a real number strictly between 0 and 1, 94 | # and controls how pointy the end result is in phase space. 95 | # Returns two all-pass filter inputs matching the specifications. 96 | # 97 | # The polynomials here should probably be treated as black magic, but 98 | # a derivation from the constraints is included below anyway. 99 | Get_panap_coeff ← { 𝕊 n‿rr‿f: 100 | PM ← +´¨+⌜○↕○≠⊔○⥊×⌜ # Multiply polynomials 101 | 102 | r ← טrr 103 | c1‿c2 ← 2×Cos¨ 1‿2 × 2×π×f÷o.freq 104 | 105 | q ← (r-1)×2÷n 106 | poly ← +´ ⟨ 107 | 4×⟨1+r+q,¯2⟩ PM (⟨r,1+r,0⟩×⟨0,6,0⟩+c2-2×c1) + ⟨1+טr,0,4⟩×1-c1 108 | ⟨1+r,¯2⟩ PM˜⊸PM (2+c1)×⟨1+r,-c1⟩ 109 | ⟩ 110 | 111 | P ← {+⟜(𝕩⊸×)´poly} 112 | v ← P¨ bnd←0‿rr 113 | ! 0≥×´v 114 | s ← -×⊑v 115 | re ← ⊑ (s×v) {u←s×P m←2÷˜+´𝕩⋄𝕊⍟(1e¯15<-˜´∘⊢)´u‿m((0≤u)⊑⌈‿⌊)¨𝕨‿𝕩} bnd 116 | 117 | sv ← (ט ÷ (4×q) + ×⟜((3-r)+2×re)) 1+r-2×re 118 | 119 | GetZ ← ⊢ ⋈ √∘-⟜(ט) # 𝕩 is real part and 𝕨 is square magnitude 120 | (GetZ○⊑ ⋈ GetZ○(⊑+sv×+´))´ ⟨r‿¯1,re‿1⟩ 121 | } 122 | 123 | # --------------------------------------------------------- 124 | " # Derivation 125 | # Transfer function for an all-pass filter: argument is z, 126 | # and rs and re are (ט|) and (2÷˜+⟜+) of the complex parameter 127 | H = (1 + (rs×ט) - 2×re⊸×) ÷ (ט + rs - 2×re⊸×) 128 | # Derivative with respect to z 129 | Hp = (2 × (re-˜rs⊸×) - H×(1-re)) ÷ (ט+rs-2×re⊸×) 130 | = (2 × (× rs-H) - re×1-˜H) ÷ (ט+rs-2×re⊸×) 131 | 132 | # Definition of the phase transfer function T 133 | (⋆⍳T ω) = (H ⋆⍳ω) 134 | # Derivative in terms of H and its derivative Hp 135 | ⍳(⋆⍳T(ω))×Tp(ω) = Hp(⋆⍳ω)×⍳⋆⍳ω 136 | Tp(ω) = Hp(⋆⍳ω) × ⋆⍳ω-T(ω) 137 | = Hp(z) × z ÷ H(z) 138 | = (2×z × (z×rs-H) - re×H-1) ÷ (1 + (rs×טs) - 2×re×z) 139 | 140 | # Simplified at 1 and ¯1 (0 and maximum frequency) 141 | H(1) = 1 142 | T(0) = 0 143 | Tp(0) = (2×rs-1) ÷ (1+rs-2×re) 144 | 145 | H(¯1) = 1 146 | T(π) = 0 147 | Tp(π) = (2×rs-1) ÷ (1+rs+2×re) 148 | 149 | 150 | # For a delay 151 | H(z) = z⋆-n 152 | T(ω) = -n×ω 153 | Tp(ω) = -n 154 | 155 | # Constraints 156 | # We refer to the forward and backwards filters with 157 | # postfixes of 1 and 2. 158 | Tp1(0) = Tp2(0) - n 159 | Tp1(π) ≃ Tp2(π) 160 | Tp1(f) = Tp2(f) 161 | 162 | # In a symmetric notation: [a,b] = (a1÷b1) - (a2÷b2) 163 | # where an and bn are the values for the nth filter. 164 | # z2 = טz 165 | (-n÷2)= [rs-1 , 1+rs-2×re] 166 | 0 = [rs-1 , 1+rs+2×re] 167 | 0 = [((z×rs-H) - re×H-1) , (1 + (z2×rs) - (2×z×re))] 168 | 169 | # Useful identity 170 | # Define (reC = re2 - re1) and (rsC = rs2 - rs1) 171 | rs1×re2 - rs2×re1 = (rs1×re1 + rs1×reC) - (rs1×re1 + rsC×re1) 172 | = rs1×reC - rsC×re1 173 | 174 | # Second constraint, where [12] indicates the value with 1 and 2 swapped 175 | ((rs1-1) × 1+rs2+2×re2) = [12] 176 | ((rs1 × 1+2×re2) - (rs2+2×re2)) = [12] 177 | (rs1 + re1 + rs1×re2) = [12] 178 | 0 = rsC + reC + (re1×rsC - reC×rs1) 179 | = (rsC × re1+1) - (reC × rs1-1) 180 | (reC ÷ re1+1) = (rsC ÷ rs1-1) 181 | 182 | # Thus, we can define s so that 183 | reC = s×re1+1 184 | rsC = s×rs1-1 185 | 186 | rs1×re2 - rs2×re1 = rs1×reC - rsC×re1 187 | = s × (rs1×re1+1) - (re1×rs1-1) 188 | = s × rs1 + re1 189 | 190 | # Cross-multiply first constraint and subtract second 191 | (rs2-rs1) = (n÷8)×(1+rs1-2×re1)×(1+rs2-2×re2) 192 | (s×rs1-1) = (n÷8)×(1+rs1-2×re1)×((1+rs1-2×re1) + s×(rs1-1)-2×(re1+1)) 193 | (s × (rs1-1)×(8÷n)) = (ט 1+rs1-2×re1) - s×(1+rs1-2×re1)×(rs1-˜3+2×re1) 194 | s = (ט 1+rs1-2×re1) ÷ (((rs1-1)×(8÷n)) + (1+rs1-2×re1)×(rs1-˜3+2×re1)) 195 | 196 | 197 | # Third constraint 198 | # Value of Tp, dropping factor of 2×z 199 | ((z×rs1-h1) + re1×h1-1) × (1 + (z2×rs2) - (2×z×re2)) = [12] 200 | (re1 -˜ z×rs1 + h1×re1-z) × (1 + (z2×rs2) - (2×z×re2)) = [12] 201 | ((z×rs1) - (re1 + z2×rs1×re2)) + (h1×re1-z)×(1 + (z2×rs2) - (2×z×re2)) = [12] 202 | ((z×rs1) - (re1 + z2×rs1×re2)) + (h1×h2×re1-z)×(rs2 + z2 - 2×z×re2) = [12] 203 | ((z×rs1) + (z2×rs2×re1) - re1) + h1×h2×((rs2×re1) + (z×rs1) - z2×re1) = [12] 204 | ((z×rsC) + (z2×(rs1×re2 - rs2×re1)) - reC) + h1×h2×((rs1×re2 - rs2×re1) + (z×rsC) - z2×reC) = 0 205 | # Divide by s 206 | ((z×rs1-1) + (z2×(rs1+re1)) - re1+1) + h1×h2×((rs1+re1) + (z×rs1-1) - z2×re1+1) = 0 207 | ((rs1×z2+z) + (re1×z2-1) - z+1) + h1×h2×((rs1×z+1) + (re1×1-z2) - z2+z) = 0 208 | # Divide by z+1 209 | ((rs1×z) + (re1×z-1) - 1) + h1×h2×(rs1 + (re1×1-z) - z) = 0 210 | h1×h2 = ((rs1×z) + (re1×z-1) - 1) ÷ ((-rs1) + (re1×z-1) + z) 211 | = 1 + (z+1)×(1-rs1) ÷ (rs1 + (re1×1-z) - z) 212 | 213 | # Value of h2 in terms of re1 and rs1 214 | # With these and the previous definition, we obtain a 4th-order 215 | # polynomial in re1 and rs1. 216 | a = 2×z×re1, r = rs1 217 | h1 = ((1 + (z2×r) - a) ) ÷ ((z2 + r - a) ) 218 | h2 = ((1 + (z2×r) - a) + s×(((z2×r) - a) - (z×z+2))) ÷ ((z2 + r - a) + s×((r - a) - (1+2×z))) 219 | 220 | # Algebra 221 | h2 = 1 + ((r-1)×(z2-1)×(1+s)) ÷ ((-a×1+s) + (z2 + r) + s×(r - (1+2×z))) 222 | = 1 + (r-1)×(z2-1) ÷ (((ט1+z)÷1+s) + (r - (1+2×z)) - a) 223 | = 1 + (r-1)×(z2-1) ÷ ((z2+r-a) - (ט1+z)×s÷1+s) 224 | h1 = 1 + (r-1)×(z2-1) ÷ (z2+r-a) 225 | s÷1+s = (ט1+r-2×e) ÷ (4 × ((r-1)×(2÷n)) + 1+r-2×e) 226 | = (ט1+r-2×e) ÷ (4 × q + 1+r-2×e) 227 | 228 | # Put each of our values in a regular form, defining k, a=b+d, b, c 229 | h2 = (1+k÷a) = 1 + (1-r)×(1-z2) ÷ ((z2+r-2×z×e) - (ט1+z)×s÷1+s) 230 | h1 = (1+k÷b) = 1 + (1-r)×(1-z2) ÷ (z2+r-2×z×e) 231 | h1×h2 = (1+k÷c) = 1 + (1-r)×(1-z2) ÷ (1-z)×((r-z) + (1-z)×e) 232 | 233 | # Simplify the overall form 234 | (1+k÷a)×(1+k÷b) = (1+k÷c) 235 | c×(a+k)×(b+k) = ab×(c+k) # Cross-multiply 236 | c×(a+b+k) = ab # Subtract abc 237 | c×k+b = a×b-c 238 | 239 | c×k+b = (1-z)×((r-z)+(1-z)×e) × ((1-r)×(1-z2))+z2+r-2×z×e 240 | = (1-z)×((r-z)+(1-z)×e) × (1+z2×r)-2×z×e 241 | a×b-c = ((z2+r-2×z×e) - (ט1+z)×s÷1+s)×((z2+r-2×z×e)-(1-z)×((r-z) + (1-z)×e)) 242 | = ((z2+r-2×z×e) - (ט1+z)×s÷1+s)×((z×1+r) - e×1+z2) 243 | 244 | q←(r-1)×(2÷n) 245 | ((1-z)×⟨r-z,1-z⟩PM⟨1+z2×r,-2×z⟩PM⟨4×q+1+r,¯8⟩) + ⟨z×1+r,-1+z2⟩ PM (PM˜(1+z)×⟨1+r,¯2⟩) - ⟨z2+r,-2×z⟩PM⟨4×q+1+r,¯8⟩ 246 | # Group q 247 | {(4×⟨q+1+r,¯2⟩PM(𝕩PM⟨z2+r,-2×z⟩)-˜(1-z)×⟨r-z,1-z⟩PM⟨1+z2×r,-2×z⟩) + 𝕩 PM PM˜(1+z)×⟨1+r,¯2⟩}⟨z×1+r,-1+z2⟩ 248 | 249 | # Expand; divide by z2; simplify 250 | zd←z+÷z ⋄ ze←z2+÷z2 251 | (4×⟨q+1+r,¯2⟩PM(⟨r,1+r,0⟩×⟨0,6,0⟩+ze-2×zd)+⟨1+טr,0,4⟩×1-zd) + (PM˜⟨1+r,¯2⟩)PM(2+zd)×⟨1+r,-zd⟩" 252 | -------------------------------------------------------------------------------- /reverb.bqn: -------------------------------------------------------------------------------- 1 | # Reverb, using FFTW if available and BQN-based FFT if not 2 | 3 | Reverb ← { 4 | lw‿lx ← (¯1⊑≢)¨ 𝕨‿𝕩 5 | ! 0" ∾ plan ← "*:i32" 24 | Fn ← "/usr/lib/libfftw3.so.3"⊸(•BQN"•FFI") 25 | createPlan ← Fn plan‿"fftw_plan_r2r_1d"‿"i32"‿"*f64"‿"&f64"‿"i32"‿"i32" 26 | destroyPlan ← Fn ""‿"fftw_destroy_plan"‿pl 27 | executePlan ← Fn ""‿"fftw_execute"‿pl 28 | FFTR ← { 𝕊⁼𝕩: 1𝕊𝕩 ; 29 | plan‿out ← CreatePlan ⟨≠𝕩,𝕩,0¨𝕩,𝕨⊣0,2⋆6⟩ 30 | ExecutePlan plan 31 | DestroyPlan plan 32 | out 33 | } 34 | { 35 | mh←-nh←1-˜2÷˜n←≠𝕗 36 | M ← (mh(↓-0∾0∾˜⌽∘↑)×) ∾ nh(⌽∘↑+-⊸↑)×⟜⌽○(1⊸↓) # Scrambled complex × 37 | (n ÷˜ FFTR 𝕗)⊸M⌾FFTR n⊸↑ 38 | } 39 | }⎊{𝕊 40 | fft ← •Import "fft.bqn" 41 | M ← -˝∘× ≍ +˝∘×⟜⌽ # Complex multiplication 42 | {⊏ · (FFT 𝕗)⊸M⌾FFT (≠𝕗)⊸↑} 43 | }@ 44 | 45 | Reverb 46 | -------------------------------------------------------------------------------- /scale.bqn: -------------------------------------------------------------------------------- 1 | # Utilities for working with notes and scales 2 | 3 | # A minor scale goes up two fifths and down four from the root 4 | minor ← ∧12|7×2-↕7 5 | 6 | # Transpose frequency 𝕩 up 𝕨 semitones. 7 | Trans ⇐ (2 ⋆ ÷⟜12)⊸× 8 | 9 | # Frequency from note in roughly scientific pitch notation 10 | # e.g. C#, A4, Gb2, F## 11 | Oct ← ⊑'4'-˜('0'⊸≤∧≤⟜'9')⊸/∾"3"˙ # Default to 3 to hit middle C 12 | Note ⇐ (minor⊑˜'a'(⊢-≤◶'A'‿⊣)⊑) (+´⟜(-˝"#b"=⌜⊢) Trans 440×2⋆Oct∘⊢) 1⊸↓ 13 | 14 | # Nearest note from frequency 15 | names ← ('A'⊸+ ∾⟜(/⟜"#")¨ ⊒) /12⊸«⊸-minor 16 | FromFreq ⇐ 12 ((names⊑˜|) ∾ '4'+⌊∘÷˜) 0.5 ⌊∘+ (440×2 ⋆ ÷⟜12)⁼ 17 | -------------------------------------------------------------------------------- /soxresample.bqn: -------------------------------------------------------------------------------- 1 | # Binding for sox's resample library 2 | 3 | u ← "u64" 4 | soxR ← "/usr/lib/libsoxr.so" •FFI ⟨ 5 | u,"soxr_oneshot","f64","f64","u32" 6 | "*f32",u,u 7 | "&f32",u,"&u64:i32" 8 | u,u,u 9 | ⟩ 10 | 11 | # Resample pcm data 𝕩 from frequency fIn to frequency fOut. 12 | { iFr‿oFr 𝕊 𝕩: 13 | { 14 | oMax ← ⌈ (oFr÷iFr) × iLen←≠𝕩 # Input and maximum output lengths 15 | s‿out‿oLen ← SoxR ⟨iFr,oFr,1, 𝕩,iLen,0, oMax⥊0,oMax,⟨1,0⟩, 0,0,0⟩ 16 | ! 0 = s 17 | (⊑oLen)↑out 18 | }⎉1⍟(iFr≠oFr) 𝕩 19 | } 20 | -------------------------------------------------------------------------------- /tracker.bqn: -------------------------------------------------------------------------------- 1 | # Sequencer/tracker based on a string pattern 2 | 3 | ⟨ 4 | SetOpts # Set global options 5 | MakeTrack # Build entire track 6 | Sequence # Build a single instrument 7 | AdvanceState # Advance state object 𝕩 past pattern 𝕨 8 | ReadPattern # Read pattern for a track from lf-separated string 9 | ⟩⇐ 10 | 11 | "tracker.bqn takes a single option namespace, or no arguments" ! 1≥≠•args 12 | op ← ≠◶⟨•Import∘"options.bqn", ⊑⟩ •args 13 | ⟨Add⟩‿⟨Lp2,Hp2⟩ ← op •Import¨ "mix.bqn"‿"filter.bqn" 14 | 15 | # Sequencing options 16 | Opts ← { 17 | shifts ← {𝕩.shifts}⎊("><]["≍÷8‿¯8‿3‿¯3) 𝕩 18 | gains ← {𝕩.gains} ⎊("-+"≍1.2⋆¯1‿1) 𝕩 19 | noop ← {𝕩.noop} ⎊"." 𝕩 20 | control ⇐ noop∾shifts∾○⊏gains # Non-sample characters 21 | GetShift‿GetVol ⇐ 0‿1 {{𝕩⊏˜𝕨⊸⊐}⟜(∾⟜𝕨)˝𝕩}¨ shifts‿gains 22 | 23 | beat ⇐ {𝕩.beat} ⎊(!˙) 𝕩 # Starting samples/beat (optional with :BEAT:) 24 | swing ⇐ {𝕩.swing}⎊⟨1⟩ 𝕩 # Modifier for length of each beat 25 | 26 | empty ⇐ {𝕩.empty}⎊(2‿0⥊0) 𝕩 27 | pink ⇐ {𝕩.pink} ⎊2 𝕩 # Level of pink noise "humanization" 28 | end ⇐ {𝕩.end} ⎊0 𝕩 # Number of additional beats at the end 29 | } 30 | o ← Opts{⇐} 31 | SetOpts ⇐ { o ↩ Opts 𝕩 } 32 | 33 | # Build a multi-track pattern 34 | MakeTrack ← { 𝕊𝕩: 35 | pattern‿sample ← 𝕩 ⋄ pattern<˘⍟(1<=)↩ # Template, and sample function 36 | state ← pattern⊢¨{⟨v⇐state⟩:v;{⇐}}𝕩 # Starting state for each track 37 | overlap ← {⟨v⇐overlap⟩:v;0¨ pattern}𝕩 # Whether adjacent samples overlap 38 | post ← {⟨v⇐post ⟩:v;⊢˙¨pattern}𝕩 # Post-processing for each channel 39 | postgroup ← {⟨v⇐postgroup⟩:v; ↕≠post}𝕩 # Group equal values: add before post-processing 40 | arg ← ⍉[pattern,sample,state,overlap] 41 | _sum ← { 𝔽_𝕣⟨x⟩: 𝔽x ; o.empty 𝔽⊸Add´ 𝕩 } 42 | { (post⊑˜⊑𝕩){𝕎𝕩} (Sequence ⊏⟜arg)_sum 𝕩 } _sum ⊔postgroup 43 | } 44 | 45 | # String handling 46 | Ex ← +`⊸×⟜¬-⊢ 47 | Cut ← Ex˜∘=⊔⊢ 48 | ReadPattern ← { ∾˘⍉> 1⊸»⊸<⊸Ex∘(0=≠¨)⊸⊔ (@+10) Cut 𝕩 } 49 | 50 | # :BEAT: indication handling 51 | Getb ← •BQN 52 | Avgb ← (+´÷≠)∘⥊ 53 | 54 | # Sound utilities 55 | Con ← ∾≍ 56 | # Pink noise with no normalization 57 | Rand ← -⟜¬ {op.RandFloats𝕩} 58 | PinkDiff ← {𝕩 ↑ ⥊∘⍉∘≍´⌽ -⟜«∘Rand¨ (1⌈↕∘⌈)⌾(2⋆⁼⊢)2⌈𝕩} 59 | 60 | # Output is ⟨list of lengths, list of characters, average lengths⟩ 61 | ParseBeats ← { 62 | m ← "Initial beat length unknown" 63 | s ← ':' Cut ' '⊸≠⊸/ 𝕩 64 | g ← (⊑1↑s) {𝕩.beat}∘𝕨⊸⋈⊸∾⍟(0<≠∘⊣) Getb¨⌾(⊏⍉) ⌊‿2⥊1↓s 65 | <∘∾˘ ⍉ (≠∘⊢ ⥊¨ ⋈∾Avgb∘⊣)´˘ g 66 | } 67 | AdvanceState ← { b 𝕊 st: 68 | offset ⇐ ({𝕩.offset}⎊0𝕨) + ⌊0.5 +´ ⊑ st ParseBeats b 69 | beat ⇐ {⊑':'∊b ? Avgb Getb 1↓ (1-˜⊢´)⊸=∘(+`':'⊸=)⊸/ b ; st.beat} 70 | } 71 | 72 | Sequence ← { 𝕊p‿s‿t:𝕊𝕩∾0 ; 𝕊 pattern‿GetSamples‿state‿overlap: 73 | # Beat length, character, average length 74 | b‿c‿a ← state ParseBeats pattern 75 | 76 | # Compute lengths 77 | b +↩ (⥊⟜(o.swing-1) + (o.pink÷100){(0≠𝕗)◶⟨0,𝕗×PinkDiff⟩})∘≠⊸× a 78 | ge ← (m∾0) (¬⊸×-⊣) g ← +`0∾˜ m ← ¬ c∊o.control 79 | sh ← +´¨ ge ⊔ 0∾˜b × o.GetShift c 80 | len ← ⌊0.5+ (-⟜»sh) + +´¨ g ⊔ b∾o.endׯ1⊏b 81 | 82 | # Samples 83 | vol ← ×´¨ ¯1↓ ge ⊔ 1∾˜o.GetVol c 84 | d ← ⟨o.empty⟩ ∾ vol × GetSamples m/c 85 | 86 | # Construct output from samples and lengths 87 | { overlap ? 88 | L‿H ← Lp2‿Hp2 {𝕩⊸𝕎⍟2}¨ 1000 89 | Overlap ← { 90 | tail ← o.empty 91 | Next ← {l𝕊𝕩: 92 | xt ← 𝕩‿tail 93 | f ← l≥¯1⊑≢𝕩 94 | tail ↩ f◶Add‿⊑ l ↓⎉1¨ f↓xt 95 | +´ l ↑⎉1¨ xt 96 | } 97 | Con 𝕨 Next¨ 𝕩 98 | } 99 | len (Overlap⟜(H¨) Add ·Con(L↑⎉1)¨) d 100 | ; 101 | Con len ↑⎉1¨ d 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /wav.bqn: -------------------------------------------------------------------------------- 1 | # Functions to read to and write from wave files. 2 | # Does not support many kinds of wave files, such as compressed data. 3 | 4 | ⟨Read, Write, Read_set, Read_coerce⟩⇐ 5 | 6 | "wav.bqn takes a single option namespace, or no arguments" ! 1≥≠•args 7 | o ← ≠◶⟨•Import∘"options.bqn", ⊑⟩ •args 8 | 9 | # The output from Read, or input to Write, is either: 10 | # - A list of: 11 | # The sample rate (in Hz) 12 | # The PCM format (see below) 13 | # PCM data, which has shape n‿l for n channels with l samples. 14 | # - The PCM data only 15 | # (options.freq and options.fmt are used for rate and format) 16 | # 17 | # Read returns the plain PCM data if the settings matched the 18 | # options while Read_set and Read_coerce always return the 19 | # plain data. 20 | 21 | # A PCM format consists of the type of audio and the bit depth. 22 | # The type is one of: 23 | # 1 unsigned integer 24 | # 3 floating point 25 | # Other audio formats may be supported in the future. 26 | 27 | # Wave file header format 28 | wh ← { 29 | # Field properties are: 30 | # len: Length of field in bytes 31 | # typ: Whether to leave as chars (c) or convert to an integer (i) 32 | # err: Behavior on invalid value: fail (e), warn (w), or ignore (.) 33 | # (Fields with ? depend on context) 34 | # name: Field name 35 | # def: How to compute value 36 | len‿typ‿err‿name‿def ⇐ <˘⍉>⟨ 37 | 4‿'c'‿'e'‿"chunkID" ‿⟨"RIFF"⟩ 38 | 4‿'i'‿'.'‿"chunkSize" ‿⟨20++´,"subchunk1Size","subchunk2Size"⟩ 39 | 4‿'c'‿'e'‿"format" ‿⟨"WAVE"⟩ 40 | 4‿'c'‿'e'‿"subchunk1ID" ‿⟨"fmt "⟩ 41 | 4‿'i'‿'?'‿"subchunk1Size"‿⟨16⟩ 42 | 2‿'i'‿'.'‿"audioFormat" ‿⟨⟩ 43 | 2‿'i'‿'.'‿"numChannels" ‿⟨⟩ 44 | 4‿'i'‿'.'‿"sampleRate" ‿⟨⟩ 45 | 4‿'i'‿'w'‿"byteRate" ‿⟨×´÷8˙,"sampleRate","numChannels","bitsPerSample"⟩ 46 | 2‿'i'‿'w'‿"blockAlign" ‿⟨×´÷8˙,"numChannels","bitsPerSample"⟩ 47 | 2‿'i'‿'.'‿"bitsPerSample"‿⟨⟩ 48 | 4‿'c'‿'?'‿"subchunk2ID" ‿⟨"data"⟩ 49 | 4‿'i'‿'.'‿"subchunk2Size"‿⟨⟩ 50 | ⟩ 51 | def ↩ name⊸⊐⌾(1⊸↓)⍟(1<≠)¨ def 52 | 53 | # Topological order for field definitions 54 | order ⇐ {{𝕊⍟(l>○≠⊢)⟜(𝕩∾·/𝕨⊸<)𝕨∨∧´∘⊏⟜𝕨¨l}⟜/0¨l←𝕩} 1↓¨def 55 | 56 | # Then fill blank definitions with a self-reference 57 | def ↩ ↕∘≠⊸({⊑‿𝕨⍟(0=≠)𝕩}¨) def 58 | 59 | # Turn list of values into namespace 60 | makeNS ⇐ •BQN "{"∾(1↓∾"‿"⊸∾¨name)∾"⇐𝕩}" 61 | } 62 | 63 | _be_ ← {1(-⊸↓-𝕗×↓)⌊∘÷⟜𝕗⍟(↕1+𝕘)} # Base expansion 64 | 65 | # Return an undoable (⁼) function to convert bytes to PCM data. 66 | _audioConvert ← { 67 | audioFormat‿bitsPerSample ← 𝕗 68 | "Bits per sample must be a multiple of 8" ! 0=8|bitsPerSample 69 | "Bits per sample cannot exceed 64" ! bitsPerSample ≤ 64 70 | l ← bitsPerSample÷8 71 | _withInv_ ← {F _𝕣_ G: {𝕊:F𝕩 ; 𝕊⁼:G𝕩}} 72 | # Convert 𝕗-byte sequences to ints 73 | bitcast ← •BQN⎊0 "•bit._cast" 74 | _int ← { 75 | 0≢bitcast ? ⊑𝕗∊1‿2‿4 ? 76 | ⟨8,'c'⟩‿⟨8×𝕗,'i'⟩ _bitcast 77 | ; 78 | b ← 256 79 | (+⟜(b⊸×)˝˜⟜(-(b÷2)≤¯1⊸⊏)·⍉⌊‿𝕗⥊⊢) _withInv_ (⥊∘⍉∘> b _be_ 𝕗) -⟜@ 80 | } 81 | # Convert int to float 82 | _float ← {e‿m‿b←𝕗 # exponent and mantissa length in bits; bias 83 | maxval←(1-2⋆-m+1)×2⋆(2⋆e)-b+1 84 | { 85 | 𝕩 ×↩ 2⋆-m 86 | p‿s ← (2⋆e) (| ⋈ ⌊∘÷˜) ⌊𝕩 87 | p +↩ ¬n←0

∨○(∨´⥊) >⟜max) Dither∘⊣⍟≢⟜⌊)⍟(f=1) pcm 121 | } 122 | 123 | Decode ← { 124 | If ← {𝕏⍟𝕎@}´ 125 | While ← {𝕨{𝕊∘𝔾⍟𝔽𝕩}𝕩@}´ 126 | # Integer from little-endian unsigned bytes 127 | ToInt ← 256⊸×⊸+˜´ -⟜@ 128 | 129 | hdrt‿dat ← wh.len +´⊸(↑⋈↓) 𝕩 130 | 131 | # Assign field values to field names. 132 | hdr ← ('i'=wh.typ) ToInt∘⊢⍟⊣¨ wh.len /⊸⊔ hdrt 133 | subchunk1Size‿subchunk2Size‿subchunk2ID‿audioFormat‿bitsPerSample‿sampleRate‿numChannels ← wh.MakeNS hdr 134 | # Handle extensible format 135 | "subchunk1Size is invalid" ! ⊑ 0‿2‿24 ∊˜ se←subchunk1Size-16 136 | ScHdr ← 4 (↑ ⋈ ToInt∘↓) ⊢ # Parse a new subchunk header 137 | If (se>0)‿{𝕤 138 | ! se = 2 + ToInt 2↑subchunk2ID 139 | ext←@ ⋄ ext‿dat ↩ se (↑⋈↓) dat 140 | subchunk2ID‿subchunk2Size ↩ ScHdr ext « ¯8 ↑ hdrt 141 | If (se>2)‿{𝕤 142 | If (audioFormat = 65534)‿{𝕤⋄ audioFormat ↩ ToInt 4↑ext } 143 | } 144 | } 145 | # Ignore remaining subchunks 146 | While {𝕤⋄"data"≢subchunk2ID}‿{𝕤 147 | (subchunk2ID‿subchunk2Size)‿dat ↩ 8 (ScHdr∘↑ ⋈ ↓) subchunk2Size ↓ dat 148 | } 149 | # Check that fields match their definitions 150 | e ← hdr ≢⟜(⊑{𝕎𝕩⊏hdr}1⊸↓)¨ wh.def 151 | Msg ← "Values for fields " ∾ (∾∾⟜" "¨) ∾ "are incorrect"˙ 152 | _alert ← {(𝔽∘Msg /⟜(wh.name))⍟(∨´) e ∧ wh.err⊸∊} 153 | !⟜0 _alert "e"∾(se<0)/"?" 154 | (•Out "Warning: "∾⊢) _alert "w" 155 | 156 | fmt ← audioFormat‿bitsPerSample 157 | Cvt ← fmt _audioConvert 158 | ⟨sampleRate, fmt, ⍉ ⌊‿numChannels ⥊ Cvt⎊(Cvt subchunk2Size⊸↑) dat⟩ 159 | } 160 | 161 | Encode ← { rate‿fmt‿pcm: 162 | ! 2 ≥ =pcm 163 | pcm ⥊⟜0⊸↓˜↩ 2 164 | dat ← fmt _audioConvert⁼ fmt ForceFormat ⥊⍉pcm 165 | iname‿ival ← <˘⍉∘‿2⥊⟨ 166 | "sampleRate" , rate 167 | "numChannels" , ≠pcm 168 | "subchunk2Size", ≠dat 169 | "audioFormat" , 0⊑fmt 170 | "bitsPerSample", 1⊑fmt 171 | ⟩ 172 | val ← def ← (⥊∘<¨ival)⌾((wh.name⊐iname)⊸⊏) wh.def 173 | 174 | { val ↩ (⊑{𝕎𝕩⊏val}1⊸↓)⌾(𝕩⊸⊑) val }¨ wh.order 175 | 176 | hdr ← ∾ (wh.len×wh.typ='i') 256{@+𝕗_be_𝕨 𝕩}⍟(>⟜0)¨ val 177 | hdr ∾ dat 178 | } 179 | 180 | ReadFull ← Decode∘{ o.FBytes 𝕩 } 181 | Read ← { ¯1⊸⊑⍟(⟨o.freq,o.fmt⟩ ≡ 2⊸↑) ReadFull 𝕩 } 182 | Write ← { 𝕩 o.FBytes Encode(⟨o.freq,o.fmt⟩∾<)⍟(1≥≡) 𝕨 } 183 | 184 | # Read, setting o.freq and o.fmt as a side effect. 185 | Read_set ← { t←ReadFull 𝕩 ⋄ o.Set ¯1↓t ⋄ ¯1⊑t } 186 | 187 | # Read, resampling to fit current frequency, using options.Resample 188 | Read_coerce ← { ((o.freq∾˜0⊸⊑) o.Resample ¯1⊸⊑) ReadFull 𝕩 } 189 | --------------------------------------------------------------------------------