@summer_icicle/time-traveler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +320 -0
- package/dist/audio/AudioPlayer.d.ts +4 -0
- package/dist/audio/AudioPlayer.js +28 -0
- package/dist/audio/AudioPlayer.js.map +1 -0
- package/dist/audio/Envelope.d.ts +9 -0
- package/dist/audio/Envelope.js +29 -0
- package/dist/audio/Envelope.js.map +1 -0
- package/dist/audio/LowPassFilter.d.ts +5 -0
- package/dist/audio/LowPassFilter.js +14 -0
- package/dist/audio/LowPassFilter.js.map +1 -0
- package/dist/audio/Oscillator.d.ts +8 -0
- package/dist/audio/Oscillator.js +42 -0
- package/dist/audio/Oscillator.js.map +1 -0
- package/dist/audio/StereoBuffer.d.ts +4 -0
- package/dist/audio/StereoBuffer.js +2 -0
- package/dist/audio/StereoBuffer.js.map +1 -0
- package/dist/audio/Synthesizer.d.ts +21 -0
- package/dist/audio/Synthesizer.js +186 -0
- package/dist/audio/Synthesizer.js.map +1 -0
- package/dist/audio/WavEncoder.d.ts +8 -0
- package/dist/audio/WavEncoder.js +56 -0
- package/dist/audio/WavEncoder.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +2 -0
- package/dist/cli/commands/analyze.js +90 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/export.d.ts +2 -0
- package/dist/cli/commands/export.js +86 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/play.d.ts +2 -0
- package/dist/cli/commands/play.js +69 -0
- package/dist/cli/commands/play.js.map +1 -0
- package/dist/git/GitCommit.d.ts +10 -0
- package/dist/git/GitCommit.js +2 -0
- package/dist/git/GitCommit.js.map +1 -0
- package/dist/git/GitHistoryAnalyzer.d.ts +7 -0
- package/dist/git/GitHistoryAnalyzer.js +97 -0
- package/dist/git/GitHistoryAnalyzer.js.map +1 -0
- package/dist/git/GitHistoryParser.d.ts +7 -0
- package/dist/git/GitHistoryParser.js +97 -0
- package/dist/git/GitHistoryParser.js.map +1 -0
- package/dist/git/GitHistoryStats.d.ts +15 -0
- package/dist/git/GitHistoryStats.js +2 -0
- package/dist/git/GitHistoryStats.js.map +1 -0
- package/dist/git/GitRepository.d.ts +10 -0
- package/dist/git/GitRepository.js +115 -0
- package/dist/git/GitRepository.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/music/MusicComposer.d.ts +13 -0
- package/dist/music/MusicComposer.js +257 -0
- package/dist/music/MusicComposer.js.map +1 -0
- package/dist/music/MusicEvent.d.ts +11 -0
- package/dist/music/MusicEvent.js +2 -0
- package/dist/music/MusicEvent.js.map +1 -0
- package/dist/music/MusicMapper.d.ts +9 -0
- package/dist/music/MusicMapper.js +67 -0
- package/dist/music/MusicMapper.js.map +1 -0
- package/dist/music/MusicProfile.d.ts +11 -0
- package/dist/music/MusicProfile.js +2 -0
- package/dist/music/MusicProfile.js.map +1 -0
- package/dist/music/midi.d.ts +2 -0
- package/dist/music/midi.js +26 -0
- package/dist/music/midi.js.map +1 -0
- package/dist/music/normalize.d.ts +1 -0
- package/dist/music/normalize.js +8 -0
- package/dist/music/normalize.js.map +1 -0
- package/dist/music/notes.d.ts +1 -0
- package/dist/music/notes.js +24 -0
- package/dist/music/notes.js.map +1 -0
- package/dist/music/scales.d.ts +2 -0
- package/dist/music/scales.js +7 -0
- package/dist/music/scales.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export class GitHistoryAnalyzer {
|
|
2
|
+
analyze(commits) {
|
|
3
|
+
if (commits.length === 0) {
|
|
4
|
+
return {
|
|
5
|
+
commitCount: 0,
|
|
6
|
+
totalInsertions: 0,
|
|
7
|
+
totalDeletions: 0,
|
|
8
|
+
totalFilesChanged: 0,
|
|
9
|
+
totalLinesChanged: 0,
|
|
10
|
+
averageInsertionsPerCommit: 0,
|
|
11
|
+
averageDeletionsPerCommit: 0,
|
|
12
|
+
averageFilesChangedPerCommit: 0,
|
|
13
|
+
averageLinesChangedPerCommit: 0,
|
|
14
|
+
repositoryAgeDays: 0,
|
|
15
|
+
commitsPerDay: 0,
|
|
16
|
+
busiestHour: null,
|
|
17
|
+
churnRatio: 0
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
let totalInsertions = 0;
|
|
21
|
+
let totalDeletions = 0;
|
|
22
|
+
let totalFilesChanged = 0;
|
|
23
|
+
const hourCounts = new Array(24).fill(0);
|
|
24
|
+
for (const commit of commits) {
|
|
25
|
+
totalInsertions += commit.insertions;
|
|
26
|
+
totalDeletions += commit.deletions;
|
|
27
|
+
totalFilesChanged += commit.filesChanged;
|
|
28
|
+
const hour = commit.date.getHours();
|
|
29
|
+
hourCounts[hour] += 1;
|
|
30
|
+
}
|
|
31
|
+
const commitCount = commits.length;
|
|
32
|
+
const totalLinesChanged = totalInsertions + totalDeletions;
|
|
33
|
+
const averageInsertionsPerCommit = totalInsertions / commitCount;
|
|
34
|
+
const averageDeletionsPerCommit = totalDeletions / commitCount;
|
|
35
|
+
const averageFilesChangedPerCommit = totalFilesChanged / commitCount;
|
|
36
|
+
const averageLinesChangedPerCommit = totalLinesChanged / commitCount;
|
|
37
|
+
const repositoryAgeDays = this.calculateRepositoryAgeDays(commits);
|
|
38
|
+
const commitsPerDay = repositoryAgeDays > 0
|
|
39
|
+
? commitCount / repositoryAgeDays
|
|
40
|
+
: commitCount;
|
|
41
|
+
const busiestHour = this.findBusiestHour(hourCounts);
|
|
42
|
+
const churnRatio = totalLinesChanged > 0
|
|
43
|
+
? totalDeletions / totalLinesChanged
|
|
44
|
+
: 0;
|
|
45
|
+
return {
|
|
46
|
+
commitCount,
|
|
47
|
+
totalInsertions,
|
|
48
|
+
totalDeletions,
|
|
49
|
+
totalFilesChanged,
|
|
50
|
+
totalLinesChanged,
|
|
51
|
+
averageInsertionsPerCommit,
|
|
52
|
+
averageDeletionsPerCommit,
|
|
53
|
+
averageFilesChangedPerCommit,
|
|
54
|
+
averageLinesChangedPerCommit,
|
|
55
|
+
repositoryAgeDays,
|
|
56
|
+
commitsPerDay,
|
|
57
|
+
busiestHour,
|
|
58
|
+
churnRatio
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
calculateRepositoryAgeDays(commits) {
|
|
62
|
+
const firstCommit = commits[0];
|
|
63
|
+
if (firstCommit === undefined) {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
let oldest = firstCommit.date.getTime();
|
|
67
|
+
let newest = oldest;
|
|
68
|
+
for (let index = 1; index < commits.length; index += 1) {
|
|
69
|
+
const commit = commits[index];
|
|
70
|
+
if (commit === undefined) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const timestamp = commit.date.getTime();
|
|
74
|
+
if (timestamp < oldest) {
|
|
75
|
+
oldest = timestamp;
|
|
76
|
+
}
|
|
77
|
+
if (timestamp > newest) {
|
|
78
|
+
newest = timestamp;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const millisecondsPerDay = 1000 * 60 * 60 * 24;
|
|
82
|
+
const difference = newest - oldest;
|
|
83
|
+
return Math.max(1, difference / millisecondsPerDay);
|
|
84
|
+
}
|
|
85
|
+
findBusiestHour(hourCounts) {
|
|
86
|
+
let busiestHour = 0;
|
|
87
|
+
let highestCount = 0;
|
|
88
|
+
for (let hour = 0; hour < hourCounts.length; hour += 1) {
|
|
89
|
+
if (hourCounts[hour] > highestCount) {
|
|
90
|
+
highestCount = hourCounts[hour];
|
|
91
|
+
busiestHour = hour;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return busiestHour;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=GitHistoryAnalyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitHistoryAnalyzer.js","sourceRoot":"","sources":["../../src/git/GitHistoryAnalyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const FIELD_SEPARATOR = "\x1f";
|
|
2
|
+
export class GitHistoryParser {
|
|
3
|
+
parse(rawHistory) {
|
|
4
|
+
return this.parseLines(rawHistory.split(/\r?\n/));
|
|
5
|
+
}
|
|
6
|
+
async parseStream(chunks) {
|
|
7
|
+
const commits = [];
|
|
8
|
+
let currentCommit = null;
|
|
9
|
+
let remainder = "";
|
|
10
|
+
for await (const chunk of chunks) {
|
|
11
|
+
const text = remainder +
|
|
12
|
+
chunk;
|
|
13
|
+
const lines = text.split(/\r?\n/);
|
|
14
|
+
remainder =
|
|
15
|
+
lines.pop() ?? "";
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
currentCommit =
|
|
18
|
+
this.processLine(line, currentCommit, commits);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (remainder.length > 0) {
|
|
22
|
+
currentCommit =
|
|
23
|
+
this.processLine(remainder, currentCommit, commits);
|
|
24
|
+
}
|
|
25
|
+
if (currentCommit !== null) {
|
|
26
|
+
commits.push(currentCommit);
|
|
27
|
+
}
|
|
28
|
+
return commits;
|
|
29
|
+
}
|
|
30
|
+
parseLines(lines) {
|
|
31
|
+
const commits = [];
|
|
32
|
+
let currentCommit = null;
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
currentCommit =
|
|
35
|
+
this.processLine(line, currentCommit, commits);
|
|
36
|
+
}
|
|
37
|
+
if (currentCommit !== null) {
|
|
38
|
+
commits.push(currentCommit);
|
|
39
|
+
}
|
|
40
|
+
return commits;
|
|
41
|
+
}
|
|
42
|
+
processLine(line, currentCommit, commits) {
|
|
43
|
+
if (line.includes(FIELD_SEPARATOR)) {
|
|
44
|
+
if (currentCommit !== null) {
|
|
45
|
+
commits.push(currentCommit);
|
|
46
|
+
}
|
|
47
|
+
const fields = line.split(FIELD_SEPARATOR);
|
|
48
|
+
const [hash, authorName, authorEmail, date, message] = fields;
|
|
49
|
+
if (hash === undefined ||
|
|
50
|
+
authorName === undefined ||
|
|
51
|
+
authorEmail === undefined ||
|
|
52
|
+
date === undefined ||
|
|
53
|
+
message === undefined) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
hash,
|
|
58
|
+
authorName,
|
|
59
|
+
authorEmail,
|
|
60
|
+
date: new Date(date),
|
|
61
|
+
message,
|
|
62
|
+
insertions: 0,
|
|
63
|
+
deletions: 0,
|
|
64
|
+
filesChanged: 0
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (currentCommit === null) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (line.length === 0) {
|
|
71
|
+
return currentCommit;
|
|
72
|
+
}
|
|
73
|
+
const parts = line.split("\t");
|
|
74
|
+
if (parts.length < 3) {
|
|
75
|
+
return currentCommit;
|
|
76
|
+
}
|
|
77
|
+
const [insertionsText, deletionsText] = parts;
|
|
78
|
+
currentCommit.filesChanged +=
|
|
79
|
+
1;
|
|
80
|
+
if (insertionsText !== "-") {
|
|
81
|
+
const insertions = Number(insertionsText);
|
|
82
|
+
if (Number.isFinite(insertions)) {
|
|
83
|
+
currentCommit.insertions +=
|
|
84
|
+
insertions;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (deletionsText !== "-") {
|
|
88
|
+
const deletions = Number(deletionsText);
|
|
89
|
+
if (Number.isFinite(deletions)) {
|
|
90
|
+
currentCommit.deletions +=
|
|
91
|
+
deletions;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return currentCommit;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=GitHistoryParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitHistoryParser.js","sourceRoot":"","sources":["../../src/git/GitHistoryParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface GitHistoryStats {
|
|
2
|
+
commitCount: number;
|
|
3
|
+
totalInsertions: number;
|
|
4
|
+
totalDeletions: number;
|
|
5
|
+
totalFilesChanged: number;
|
|
6
|
+
totalLinesChanged: number;
|
|
7
|
+
averageInsertionsPerCommit: number;
|
|
8
|
+
averageDeletionsPerCommit: number;
|
|
9
|
+
averageFilesChangedPerCommit: number;
|
|
10
|
+
averageLinesChangedPerCommit: number;
|
|
11
|
+
repositoryAgeDays: number;
|
|
12
|
+
commitsPerDay: number;
|
|
13
|
+
busiestHour: number | null;
|
|
14
|
+
churnRatio: number;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitHistoryStats.js","sourceRoot":"","sources":["../../src/git/GitHistoryStats.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class GitRepository {
|
|
2
|
+
private readonly workingDirectory;
|
|
3
|
+
constructor(workingDirectory: string);
|
|
4
|
+
isRepository(): Promise<boolean>;
|
|
5
|
+
hasCommits(): Promise<boolean>;
|
|
6
|
+
getCurrentBranch(): Promise<string>;
|
|
7
|
+
branchExists(branch: string): Promise<boolean>;
|
|
8
|
+
streamHistory(branch?: string): AsyncGenerator<string>;
|
|
9
|
+
private createLogArguments;
|
|
10
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export class GitRepository {
|
|
5
|
+
workingDirectory;
|
|
6
|
+
constructor(workingDirectory) {
|
|
7
|
+
this.workingDirectory = workingDirectory;
|
|
8
|
+
}
|
|
9
|
+
async isRepository() {
|
|
10
|
+
try {
|
|
11
|
+
await execFileAsync("git", [
|
|
12
|
+
"rev-parse",
|
|
13
|
+
"--is-inside-work-tree"
|
|
14
|
+
], {
|
|
15
|
+
cwd: this.workingDirectory
|
|
16
|
+
});
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async hasCommits() {
|
|
24
|
+
try {
|
|
25
|
+
await execFileAsync("git", [
|
|
26
|
+
"rev-parse",
|
|
27
|
+
"--verify",
|
|
28
|
+
"HEAD"
|
|
29
|
+
], {
|
|
30
|
+
cwd: this.workingDirectory
|
|
31
|
+
});
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async getCurrentBranch() {
|
|
39
|
+
const { stdout } = await execFileAsync("git", [
|
|
40
|
+
"branch",
|
|
41
|
+
"--show-current"
|
|
42
|
+
], {
|
|
43
|
+
cwd: this.workingDirectory
|
|
44
|
+
});
|
|
45
|
+
const branch = stdout.trim();
|
|
46
|
+
if (branch.length > 0) {
|
|
47
|
+
return branch;
|
|
48
|
+
}
|
|
49
|
+
const { stdout: shortHash } = await execFileAsync("git", [
|
|
50
|
+
"rev-parse",
|
|
51
|
+
"--short",
|
|
52
|
+
"HEAD"
|
|
53
|
+
], {
|
|
54
|
+
cwd: this.workingDirectory
|
|
55
|
+
});
|
|
56
|
+
return `detached-${shortHash.trim()}`;
|
|
57
|
+
}
|
|
58
|
+
async branchExists(branch) {
|
|
59
|
+
try {
|
|
60
|
+
await execFileAsync("git", [
|
|
61
|
+
"show-ref",
|
|
62
|
+
"--verify",
|
|
63
|
+
"--quiet",
|
|
64
|
+
`refs/heads/${branch}`
|
|
65
|
+
], {
|
|
66
|
+
cwd: this.workingDirectory
|
|
67
|
+
});
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async *streamHistory(branch) {
|
|
75
|
+
const args = this.createLogArguments(branch);
|
|
76
|
+
const git = spawn("git", args, {
|
|
77
|
+
cwd: this.workingDirectory,
|
|
78
|
+
stdio: [
|
|
79
|
+
"ignore",
|
|
80
|
+
"pipe",
|
|
81
|
+
"pipe"
|
|
82
|
+
]
|
|
83
|
+
});
|
|
84
|
+
let stderr = "";
|
|
85
|
+
git.stderr.setEncoding("utf8");
|
|
86
|
+
git.stderr.on("data", (chunk) => {
|
|
87
|
+
stderr += chunk;
|
|
88
|
+
});
|
|
89
|
+
const exitPromise = new Promise((resolve, reject) => {
|
|
90
|
+
git.on("error", reject);
|
|
91
|
+
git.on("close", resolve);
|
|
92
|
+
});
|
|
93
|
+
git.stdout.setEncoding("utf8");
|
|
94
|
+
for await (const chunk of git.stdout) {
|
|
95
|
+
yield chunk;
|
|
96
|
+
}
|
|
97
|
+
const exitCode = await exitPromise;
|
|
98
|
+
if (exitCode !== 0) {
|
|
99
|
+
throw new Error(stderr ||
|
|
100
|
+
`Git exited with code ${exitCode}.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
createLogArguments(branch) {
|
|
104
|
+
const args = [
|
|
105
|
+
"log",
|
|
106
|
+
"--format=%H%x1f%an%x1f%ae%x1f%aI%x1f%s",
|
|
107
|
+
"--numstat"
|
|
108
|
+
];
|
|
109
|
+
if (branch !== undefined) {
|
|
110
|
+
args.push(`refs/heads/${branch}`);
|
|
111
|
+
}
|
|
112
|
+
return args;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=GitRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitRepository.js","sourceRoot":"","sources":["../../src/git/GitRepository.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { registerAnalyzeCommand } from "./cli/commands/analyze.js";
|
|
4
|
+
import { registerExportCommand } from "./cli/commands/export.js";
|
|
5
|
+
import { registerPlayCommand } from "./cli/commands/play.js";
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name("time-traveler")
|
|
9
|
+
.description("Turn your Git history into music")
|
|
10
|
+
.version("0.1.0");
|
|
11
|
+
registerAnalyzeCommand(program);
|
|
12
|
+
registerExportCommand(program);
|
|
13
|
+
registerPlayCommand(program);
|
|
14
|
+
program.parseAsync();
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,eAAe,CAAC;KACrB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAE7B,OAAO,CAAC,UAAU,EAAE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GitCommit } from "../git/GitCommit.js";
|
|
2
|
+
import type { MusicProfile } from "./MusicProfile.js";
|
|
3
|
+
import type { MusicEvent } from "./MusicEvent.js";
|
|
4
|
+
export declare class MusicComposer {
|
|
5
|
+
compose(profile: MusicProfile, commits: GitCommit[]): MusicEvent[];
|
|
6
|
+
private aggregateLargeHistory;
|
|
7
|
+
private chooseScaleDegree;
|
|
8
|
+
private chooseOctave;
|
|
9
|
+
private calculateTensionOffset;
|
|
10
|
+
private calculateMelodyDuration;
|
|
11
|
+
private calculateAmplitude;
|
|
12
|
+
private calculateSpacing;
|
|
13
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { SCALE_INTERVALS } from "./scales.js";
|
|
2
|
+
import { noteToMidi, midiToFrequency } from "./midi.js";
|
|
3
|
+
import { normalize } from "./normalize.js";
|
|
4
|
+
export class MusicComposer {
|
|
5
|
+
compose(profile, commits) {
|
|
6
|
+
if (commits.length === 0) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
const events = [];
|
|
10
|
+
const rootMidi = noteToMidi(profile.rootNote, 4);
|
|
11
|
+
const scaleIntervals = SCALE_INTERVALS[profile.scale];
|
|
12
|
+
const secondsPerBeat = 60 / profile.tempo;
|
|
13
|
+
const chronologicalCommits = [...commits].sort((a, b) => a.date.getTime() -
|
|
14
|
+
b.date.getTime());
|
|
15
|
+
const musicalCommits = this.aggregateLargeHistory(chronologicalCommits);
|
|
16
|
+
let maximumLinesChanged = 1;
|
|
17
|
+
let maximumFilesChanged = 1;
|
|
18
|
+
for (const commit of musicalCommits) {
|
|
19
|
+
const linesChanged = commit.insertions +
|
|
20
|
+
commit.deletions;
|
|
21
|
+
if (linesChanged >
|
|
22
|
+
maximumLinesChanged) {
|
|
23
|
+
maximumLinesChanged =
|
|
24
|
+
linesChanged;
|
|
25
|
+
}
|
|
26
|
+
if (commit.filesChanged >
|
|
27
|
+
maximumFilesChanged) {
|
|
28
|
+
maximumFilesChanged =
|
|
29
|
+
commit.filesChanged;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let currentTime = 0;
|
|
33
|
+
for (let index = 0; index < musicalCommits.length; index += 1) {
|
|
34
|
+
const commit = musicalCommits[index];
|
|
35
|
+
if (commit === undefined) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const linesChanged = commit.insertions +
|
|
39
|
+
commit.deletions;
|
|
40
|
+
const size = normalize(linesChanged, 0, maximumLinesChanged);
|
|
41
|
+
const fileComplexity = normalize(commit.filesChanged, 0, maximumFilesChanged);
|
|
42
|
+
const deletionRatio = linesChanged > 0
|
|
43
|
+
? commit.deletions /
|
|
44
|
+
linesChanged
|
|
45
|
+
: 0;
|
|
46
|
+
const scaleDegree = this.chooseScaleDegree(commit, scaleIntervals.length);
|
|
47
|
+
const melodyOctave = this.chooseOctave(commit);
|
|
48
|
+
const melodyMidi = rootMidi +
|
|
49
|
+
scaleIntervals[scaleDegree] +
|
|
50
|
+
(melodyOctave - 4) *
|
|
51
|
+
12 +
|
|
52
|
+
this.calculateTensionOffset(deletionRatio);
|
|
53
|
+
const melodyFrequency = midiToFrequency(melodyMidi);
|
|
54
|
+
const melodyDuration = this.calculateMelodyDuration(secondsPerBeat, fileComplexity);
|
|
55
|
+
const melodyAmplitude = this.calculateAmplitude(profile, size, "melody");
|
|
56
|
+
events.push({
|
|
57
|
+
startTime: currentTime,
|
|
58
|
+
duration: melodyDuration,
|
|
59
|
+
frequency: melodyFrequency,
|
|
60
|
+
amplitude: melodyAmplitude,
|
|
61
|
+
waveform: profile.waveform,
|
|
62
|
+
layer: "melody",
|
|
63
|
+
commitHash: commit.hash
|
|
64
|
+
});
|
|
65
|
+
const bassMidi = rootMidi -
|
|
66
|
+
12 +
|
|
67
|
+
scaleIntervals[scaleDegree %
|
|
68
|
+
scaleIntervals.length];
|
|
69
|
+
events.push({
|
|
70
|
+
startTime: currentTime,
|
|
71
|
+
duration: secondsPerBeat *
|
|
72
|
+
(2 +
|
|
73
|
+
fileComplexity),
|
|
74
|
+
frequency: midiToFrequency(bassMidi),
|
|
75
|
+
amplitude: this.calculateAmplitude(profile, size, "bass"),
|
|
76
|
+
waveform: "sine",
|
|
77
|
+
layer: "bass",
|
|
78
|
+
commitHash: commit.hash
|
|
79
|
+
});
|
|
80
|
+
const padDuration = secondsPerBeat *
|
|
81
|
+
(3 +
|
|
82
|
+
fileComplexity *
|
|
83
|
+
3);
|
|
84
|
+
const chordDegrees = [
|
|
85
|
+
scaleDegree,
|
|
86
|
+
scaleDegree + 2,
|
|
87
|
+
scaleDegree + 4
|
|
88
|
+
];
|
|
89
|
+
for (const chordDegree of chordDegrees) {
|
|
90
|
+
const wrappedDegree = chordDegree %
|
|
91
|
+
scaleIntervals.length;
|
|
92
|
+
const octaveShift = Math.floor(chordDegree /
|
|
93
|
+
scaleIntervals.length) *
|
|
94
|
+
12;
|
|
95
|
+
const padMidi = rootMidi +
|
|
96
|
+
scaleIntervals[wrappedDegree] +
|
|
97
|
+
octaveShift;
|
|
98
|
+
events.push({
|
|
99
|
+
startTime: currentTime,
|
|
100
|
+
duration: padDuration,
|
|
101
|
+
frequency: midiToFrequency(padMidi),
|
|
102
|
+
amplitude: this.calculateAmplitude(profile, size, "pad"),
|
|
103
|
+
waveform: "sine",
|
|
104
|
+
layer: "pad",
|
|
105
|
+
commitHash: commit.hash
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
currentTime +=
|
|
109
|
+
this.calculateSpacing(musicalCommits, index, secondsPerBeat, commits.length);
|
|
110
|
+
}
|
|
111
|
+
return events;
|
|
112
|
+
}
|
|
113
|
+
aggregateLargeHistory(commits) {
|
|
114
|
+
const aggregationThreshold = 1000;
|
|
115
|
+
if (commits.length <=
|
|
116
|
+
aggregationThreshold) {
|
|
117
|
+
return commits;
|
|
118
|
+
}
|
|
119
|
+
const targetCount = Math.round(600 +
|
|
120
|
+
Math.log10(commits.length /
|
|
121
|
+
aggregationThreshold) *
|
|
122
|
+
120);
|
|
123
|
+
const bucketSize = Math.ceil(commits.length /
|
|
124
|
+
targetCount);
|
|
125
|
+
const aggregated = [];
|
|
126
|
+
for (let start = 0; start < commits.length; start += bucketSize) {
|
|
127
|
+
const end = Math.min(start +
|
|
128
|
+
bucketSize, commits.length);
|
|
129
|
+
const first = commits[start];
|
|
130
|
+
const last = commits[end - 1];
|
|
131
|
+
if (first === undefined ||
|
|
132
|
+
last === undefined) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
let insertions = 0;
|
|
136
|
+
let deletions = 0;
|
|
137
|
+
let filesChanged = 0;
|
|
138
|
+
let timestampTotal = 0;
|
|
139
|
+
let commitCount = 0;
|
|
140
|
+
for (let index = start; index < end; index += 1) {
|
|
141
|
+
const commit = commits[index];
|
|
142
|
+
if (commit === undefined) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
insertions +=
|
|
146
|
+
commit.insertions;
|
|
147
|
+
deletions +=
|
|
148
|
+
commit.deletions;
|
|
149
|
+
filesChanged +=
|
|
150
|
+
commit.filesChanged;
|
|
151
|
+
timestampTotal +=
|
|
152
|
+
commit.date.getTime();
|
|
153
|
+
commitCount += 1;
|
|
154
|
+
}
|
|
155
|
+
if (commitCount === 0) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const averageTimestamp = timestampTotal /
|
|
159
|
+
commitCount;
|
|
160
|
+
aggregated.push({
|
|
161
|
+
hash: `${first.hash.slice(0, 7)}-${last.hash.slice(0, 7)}`,
|
|
162
|
+
authorName: "Aggregated",
|
|
163
|
+
authorEmail: "",
|
|
164
|
+
date: new Date(averageTimestamp),
|
|
165
|
+
message: `Aggregated ${commitCount} commits`,
|
|
166
|
+
insertions,
|
|
167
|
+
deletions,
|
|
168
|
+
filesChanged
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return aggregated;
|
|
172
|
+
}
|
|
173
|
+
chooseScaleDegree(commit, scaleLength) {
|
|
174
|
+
const value = commit.insertions +
|
|
175
|
+
commit.filesChanged;
|
|
176
|
+
return (value %
|
|
177
|
+
scaleLength);
|
|
178
|
+
}
|
|
179
|
+
chooseOctave(commit) {
|
|
180
|
+
const hour = commit.date.getHours();
|
|
181
|
+
if (hour < 6) {
|
|
182
|
+
return 3;
|
|
183
|
+
}
|
|
184
|
+
if (hour < 12) {
|
|
185
|
+
return 4;
|
|
186
|
+
}
|
|
187
|
+
if (hour < 18) {
|
|
188
|
+
return 5;
|
|
189
|
+
}
|
|
190
|
+
return 4;
|
|
191
|
+
}
|
|
192
|
+
calculateTensionOffset(deletionRatio) {
|
|
193
|
+
if (deletionRatio >=
|
|
194
|
+
0.6) {
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
197
|
+
if (deletionRatio >=
|
|
198
|
+
0.35) {
|
|
199
|
+
return -1;
|
|
200
|
+
}
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
calculateMelodyDuration(secondsPerBeat, fileComplexity) {
|
|
204
|
+
return (secondsPerBeat *
|
|
205
|
+
(0.75 +
|
|
206
|
+
fileComplexity *
|
|
207
|
+
1.5));
|
|
208
|
+
}
|
|
209
|
+
calculateAmplitude(profile, commitSize, layer) {
|
|
210
|
+
const base = 0.12 +
|
|
211
|
+
profile.intensity *
|
|
212
|
+
0.18 +
|
|
213
|
+
commitSize *
|
|
214
|
+
0.18;
|
|
215
|
+
switch (layer) {
|
|
216
|
+
case "melody":
|
|
217
|
+
return Math.min(base + 0.08, 0.5);
|
|
218
|
+
case "bass":
|
|
219
|
+
return Math.min(base * 0.75, 0.35);
|
|
220
|
+
case "pad":
|
|
221
|
+
return Math.min(base * 0.35, 0.16);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
calculateSpacing(commits, index, secondsPerBeat, totalCommitCount) {
|
|
225
|
+
const current = commits[index];
|
|
226
|
+
const next = commits[index + 1];
|
|
227
|
+
if (current === undefined ||
|
|
228
|
+
next === undefined) {
|
|
229
|
+
return secondsPerBeat;
|
|
230
|
+
}
|
|
231
|
+
const millisecondsBetween = next.date.getTime() -
|
|
232
|
+
current.date.getTime();
|
|
233
|
+
const hoursBetween = Math.max(0, millisecondsBetween /
|
|
234
|
+
(1000 *
|
|
235
|
+
60 *
|
|
236
|
+
60));
|
|
237
|
+
const compressedGap = Math.log1p(hoursBetween);
|
|
238
|
+
const maximumReferenceGap = Math.log1p(24 * 365);
|
|
239
|
+
const normalizedGap = Math.min(1, compressedGap /
|
|
240
|
+
maximumReferenceGap);
|
|
241
|
+
const sizeCompression = totalCommitCount <=
|
|
242
|
+
100
|
|
243
|
+
? 1
|
|
244
|
+
: 1 /
|
|
245
|
+
(1 +
|
|
246
|
+
Math.log10(totalCommitCount /
|
|
247
|
+
100) *
|
|
248
|
+
0.18);
|
|
249
|
+
const baseSpacing = secondsPerBeat *
|
|
250
|
+
(1 +
|
|
251
|
+
normalizedGap *
|
|
252
|
+
4);
|
|
253
|
+
return (baseSpacing *
|
|
254
|
+
sizeCompression);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=MusicComposer.js.map
|