-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
184 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
function setCString(view: DataView, offset: number, str: string, length: number) { | ||
for (let i = 0; i < length; i++) { | ||
if (i < str.length) { | ||
view.setUint8(offset + i, str.charCodeAt(i)); | ||
} else { | ||
view.setUint8(offset + i, 0); | ||
} | ||
} | ||
} | ||
|
||
function numberToOctalString(num: number, length: number) { | ||
const str = num.toString(8); | ||
return "0".repeat(length - str.length) + str; | ||
} | ||
|
||
export class TarFile { | ||
constructor() { | ||
this.tarHead = 0; | ||
} | ||
|
||
tarHead: number; | ||
|
||
fileHeader(filename: string, size: number, mtime: Date) { | ||
const header = new ArrayBuffer(512); | ||
const view = new DataView(header); | ||
|
||
setCString(view, 0, filename, 100); // File name | ||
setCString(view, 100, "0000777", 8); // File mode | ||
setCString(view, 108, "0000000", 8); // Owner ID | ||
setCString(view, 116, "0000000", 8); // Group ID | ||
setCString(view, 124, numberToOctalString(size, 11), 12); // File size | ||
const time = Math.floor(mtime.getTime() / 1000); | ||
setCString(view, 136, numberToOctalString(time, 11), 12); // Modification time | ||
setCString(view, 148, " ", 8); // Checksum (filled in later) | ||
setCString(view, 156, "0", 1); // Type (0 = regular file) | ||
setCString(view, 157, "", 100); // Link name | ||
setCString(view, 257, "ustar", 6); // Magic | ||
setCString(view, 263, "00", 2); // Version (no null byte) | ||
setCString(view, 265, "", 32); // Owner name | ||
setCString(view, 297, "", 32); // Group name | ||
setCString(view, 329, "0000000", 8); // Device major number | ||
setCString(view, 337, "0000000", 8); // Device minor number | ||
|
||
// Fill in checksum | ||
let checksum = 0; | ||
for (let i = 0; i < 512; i++) { | ||
checksum += view.getUint8(i); | ||
} | ||
setCString(view, 148, numberToOctalString(checksum, 6), 7); | ||
|
||
return new Uint8Array(header); | ||
} | ||
|
||
fileData(buf: Uint8Array) { | ||
this.tarHead += buf.length; | ||
return buf; | ||
} | ||
|
||
fileEnd() { | ||
const remainder = 512 - (this.tarHead % 512); | ||
const block = new ArrayBuffer(remainder); | ||
const view = new DataView(block); | ||
setCString(view, 0, "", remainder); // Fill with null bytes | ||
|
||
this.tarHead = 0; | ||
|
||
return new Uint8Array(block); | ||
} | ||
|
||
tarEnd() { | ||
const block = new ArrayBuffer(1024); | ||
const view = new DataView(block); | ||
setCString(view, 0, "", 1024); // Fill with null bytes | ||
return new Uint8Array(block); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { Exporter, StreamSource } from "../Exporter"; | ||
import * as AudioWorkerComms from "../../AudioWorkerComms"; | ||
import * as WaveFile from "../wave/WaveFile"; | ||
import { TarFile } from "./TarFile"; | ||
|
||
function trackBitmaskToArray(mask: number) { | ||
const arr: number[] = []; | ||
for (let i = 0; i < 16; i++) { | ||
if (mask & (1 << i)) { | ||
arr.push(i); | ||
} | ||
} | ||
return arr; | ||
} | ||
|
||
export const wavePerChannelExporter: Exporter = { | ||
name: "Wave (per channel)", | ||
storageTag: "wavePerChannel", | ||
mimeType: "application/x-tar", | ||
fileExtension: "tar", | ||
getStream(sampleRate, seconds, onProgress, config) { | ||
const numSamples = Math.floor(sampleRate * seconds); | ||
let i = 0; | ||
let tracksToExport: number[]; | ||
let trackIndex = -1; // because we start with master.wav | ||
const tar = new TarFile(); | ||
|
||
const stream: StreamSource = { | ||
async start(controller) { | ||
await AudioWorkerComms.call("startExport", { sampleRate }); | ||
const allocatedTracks = await AudioWorkerComms.call("exportFindAllocatedTracks"); | ||
tracksToExport = trackBitmaskToArray(allocatedTracks); | ||
|
||
const tarHeader = tar.fileHeader("master.wav", WaveFile.getFileSize(numSamples, 2), new Date()); | ||
controller.enqueue(tarHeader); | ||
|
||
const waveHeader = WaveFile.header(numSamples, 2, sampleRate); | ||
controller.enqueue(tar.fileData(waveHeader)); | ||
}, | ||
async pull(controller) { | ||
const pcmBuf: Float32Array[] = await AudioWorkerComms.call("exportTickUntilBuffer"); | ||
const interleavedBuf = WaveFile.interleave(pcmBuf); | ||
|
||
i += pcmBuf[0].length; | ||
if (i > numSamples) { | ||
// Done with the current file, strip the data that's above the size of the file | ||
const samplesToRemove = i - numSamples; | ||
const newBuf = interleavedBuf.slice(0, interleavedBuf.length - samplesToRemove * 2); | ||
const byteBuf = new Uint8Array(newBuf.buffer); | ||
|
||
controller.enqueue(tar.fileData(byteBuf)); | ||
controller.enqueue(tar.fileEnd()); | ||
|
||
if (trackIndex < tracksToExport.length - 1) { | ||
// Start the next track | ||
trackIndex++; | ||
await AudioWorkerComms.call("startExport", { sampleRate, activeTracks: 1 << tracksToExport[trackIndex] }); | ||
i = 0; | ||
|
||
// Add the header for the next track | ||
const trackName = `track${tracksToExport[trackIndex]}.wav`; | ||
const tarHeader = tar.fileHeader(trackName, WaveFile.getFileSize(numSamples, 2), new Date()); | ||
controller.enqueue(tarHeader); | ||
|
||
const waveHeader = WaveFile.header(numSamples, 2, sampleRate); | ||
controller.enqueue(tar.fileData(waveHeader)); | ||
} else { | ||
// All tracks are done | ||
controller.enqueue(tar.tarEnd()); | ||
controller.close(); | ||
} | ||
} else { | ||
// Not done, add the whole buffer | ||
const byteBuf = new Uint8Array(interleavedBuf.buffer); | ||
|
||
// Assumes that the buffer is a multiple of 512 bytes (which it is, see AudioWorker) | ||
controller.enqueue(tar.fileData(byteBuf)); | ||
onProgress((i / numSamples + trackIndex + 1) / (tracksToExport.length + 1)); | ||
} | ||
}, | ||
cancel() { | ||
|
||
} | ||
}; | ||
|
||
return stream; | ||
} | ||
} |