Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | import { SoundSystem } from "../digital/audio"; import { createPlayHeadClock, toSignature, Signature, createTempo, TickCallbackContainer } from "../metronome"; import { samplePlayer } from "./audio"; import { Song } from "./types"; import { SoundOn, NoOp, Tracker, Track } from "../digital/types"; type MODSong = { readonly song: Song; /** 4/4, 4/8, 7/12, etc */ timeSignature: Signature; /** Beats per minute. Mod track commands can (and will) * change this value. */ currentBpm: number currentPattern: number, currentRow: number, } export type MODRunWithTracks = MODSong & Tracker; export type MODRun = MODRunWithTracks & TickCallbackContainer; /////////////////////////////////////////// export function createRun(system: SoundSystem, song: Song): MODRun { if (song.type !== 'M.K.' && song.type !== 'FLT4') { throw new Error("Currently only support M.K mod files (4 tracks)"); } const run: MODRun = { song: song, timeSignature: toSignature("4/4"), currentBpm: 125, currentPattern: 0, currentRow: 0, tracks: [], output: system.context().createGain(), tickListeners: [], }; for (let i = 0; i < 4; i++) { const pan = system.context().createPanner(); pan.orientationX.setValueAtTime(0, 0); pan.orientationY.setValueAtTime(0, 0); pan.orientationZ.setValueAtTime(-1, 0); pan.positionX.setValueAtTime(0, 0); pan.positionY.setValueAtTime(0, 0); pan.positionZ.setValueAtTime(0, 0); const vol = system.context().createGain(); pan.connect(vol); vol.connect(run.output); vol.gain.setValueAtTime(1, 0); const t = { // TODO: wrong now with the type change lastFn: [[NoOp]], idx: 0, vol, pan, } as Track; run.tracks.push(t); } // connect the song output to the speakers run.output.connect(system.masterGainNode()); return run; } /** Clean up after the song run has played */ export function destroyRun(system: SoundSystem, run: MODRun) { run.output.disconnect(system.masterGainNode()); } const flushTracks = (run: MODRun) => { for (let i = 0; i < 4; i++) { run.tracks[i].lastFn[0][0](); run.tracks[i].lastFn[0][0] = NoOp; } } const useInstrument = (system: SoundSystem, run: MODRun, which: number): SoundOn => { return samplePlayer(system, run.song, which); }; export function insertRun(system: SoundSystem, run: MODRun) { let tempo = createTempo( 480, 500000, // default 120bpm ); run.tickListeners.push( (tick, time, bi) => { // run.currentBpm = run.bpm; // + 65; // Metronome example if (bi.beat === 1 && bi.tick === 0) { // random("A-5", time, .2); } // A row should play on a beat. if (bi.tick % 4 === 0) { // random("Eb5", time, .2); const pos = run.currentRow; // // position should advance by one row on a beat if (pos < 64) { // const l = run.currentBpm / 60 / 4; for (let c = 0; c < 4; c++) { const n1 = run.song.patterns[run.currentPattern][run.currentRow][c]; /////////////////////////////////////////// // Look at all commands... if (n1.effect.command === 0xF) { // tempo /* * 00 -\ * 01 | One beat, 4 division, * 02 | ticks in between (default 6) * 03 -/ * * 24 * beats/minute * divisions/minute = ----------------- * ticks/division */ let z = n1.effect.argX * 16 + n1.effect.argY; if (z === 0) z = 1; if (z <= 32) { // ticks / divisions tempo = createTempo(z * 6, tempo.tempo); // run.timeSignature.noteBeats = z; } else { console.log("bpm:", z, z + 65); // run.currentBpm = z; // + 65; } } if (n1.effect.command === 0xC) { // volume const volume = (n1.effect.argX * 16 + n1.effect.argY) / 64; run.tracks[c].vol.gain.setValueAtTime(volume, time); } /////////////////////////////////////////// // run.tracks[c].vol.gain.setValueAtTime(1, time); // const p = c % 2 === 0 ? 1 : -1; // run.tracks[c].pan.positionX.setValueAtTime(p, time); if (n1.note !== "") { const instrument = useInstrument(system, run, n1.sample - 1); run.tracks[c].lastFn[0][0](); run.tracks[c].lastFn[0][0] = NoOp; run.tracks[c].lastFn[0][0] = instrument(n1.note, time, run.tracks[c].pan); } } // TODO: cycle patterns... run.currentRow = pos + 1; } else if (pos === 64) { flushTracks(run); // TODO: on done... } } // console.log(run.currentPattern.toString(16), run.currentRow.toString(16)); return [tempo, run.timeSignature]; } ); createPlayHeadClock(system, tempo, run.timeSignature, run); } |