@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.
Files changed (75) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +320 -0
  3. package/dist/audio/AudioPlayer.d.ts +4 -0
  4. package/dist/audio/AudioPlayer.js +28 -0
  5. package/dist/audio/AudioPlayer.js.map +1 -0
  6. package/dist/audio/Envelope.d.ts +9 -0
  7. package/dist/audio/Envelope.js +29 -0
  8. package/dist/audio/Envelope.js.map +1 -0
  9. package/dist/audio/LowPassFilter.d.ts +5 -0
  10. package/dist/audio/LowPassFilter.js +14 -0
  11. package/dist/audio/LowPassFilter.js.map +1 -0
  12. package/dist/audio/Oscillator.d.ts +8 -0
  13. package/dist/audio/Oscillator.js +42 -0
  14. package/dist/audio/Oscillator.js.map +1 -0
  15. package/dist/audio/StereoBuffer.d.ts +4 -0
  16. package/dist/audio/StereoBuffer.js +2 -0
  17. package/dist/audio/StereoBuffer.js.map +1 -0
  18. package/dist/audio/Synthesizer.d.ts +21 -0
  19. package/dist/audio/Synthesizer.js +186 -0
  20. package/dist/audio/Synthesizer.js.map +1 -0
  21. package/dist/audio/WavEncoder.d.ts +8 -0
  22. package/dist/audio/WavEncoder.js +56 -0
  23. package/dist/audio/WavEncoder.js.map +1 -0
  24. package/dist/cli/commands/analyze.d.ts +2 -0
  25. package/dist/cli/commands/analyze.js +90 -0
  26. package/dist/cli/commands/analyze.js.map +1 -0
  27. package/dist/cli/commands/export.d.ts +2 -0
  28. package/dist/cli/commands/export.js +86 -0
  29. package/dist/cli/commands/export.js.map +1 -0
  30. package/dist/cli/commands/play.d.ts +2 -0
  31. package/dist/cli/commands/play.js +69 -0
  32. package/dist/cli/commands/play.js.map +1 -0
  33. package/dist/git/GitCommit.d.ts +10 -0
  34. package/dist/git/GitCommit.js +2 -0
  35. package/dist/git/GitCommit.js.map +1 -0
  36. package/dist/git/GitHistoryAnalyzer.d.ts +7 -0
  37. package/dist/git/GitHistoryAnalyzer.js +97 -0
  38. package/dist/git/GitHistoryAnalyzer.js.map +1 -0
  39. package/dist/git/GitHistoryParser.d.ts +7 -0
  40. package/dist/git/GitHistoryParser.js +97 -0
  41. package/dist/git/GitHistoryParser.js.map +1 -0
  42. package/dist/git/GitHistoryStats.d.ts +15 -0
  43. package/dist/git/GitHistoryStats.js +2 -0
  44. package/dist/git/GitHistoryStats.js.map +1 -0
  45. package/dist/git/GitRepository.d.ts +10 -0
  46. package/dist/git/GitRepository.js +115 -0
  47. package/dist/git/GitRepository.js.map +1 -0
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js +15 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/music/MusicComposer.d.ts +13 -0
  52. package/dist/music/MusicComposer.js +257 -0
  53. package/dist/music/MusicComposer.js.map +1 -0
  54. package/dist/music/MusicEvent.d.ts +11 -0
  55. package/dist/music/MusicEvent.js +2 -0
  56. package/dist/music/MusicEvent.js.map +1 -0
  57. package/dist/music/MusicMapper.d.ts +9 -0
  58. package/dist/music/MusicMapper.js +67 -0
  59. package/dist/music/MusicMapper.js.map +1 -0
  60. package/dist/music/MusicProfile.d.ts +11 -0
  61. package/dist/music/MusicProfile.js +2 -0
  62. package/dist/music/MusicProfile.js.map +1 -0
  63. package/dist/music/midi.d.ts +2 -0
  64. package/dist/music/midi.js +26 -0
  65. package/dist/music/midi.js.map +1 -0
  66. package/dist/music/normalize.d.ts +1 -0
  67. package/dist/music/normalize.js +8 -0
  68. package/dist/music/normalize.js.map +1 -0
  69. package/dist/music/notes.d.ts +1 -0
  70. package/dist/music/notes.js +24 -0
  71. package/dist/music/notes.js.map +1 -0
  72. package/dist/music/scales.d.ts +2 -0
  73. package/dist/music/scales.js +7 -0
  74. package/dist/music/scales.js.map +1 -0
  75. 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,7 @@
1
+ import type { GitCommit } from "./GitCommit.js";
2
+ export declare class GitHistoryParser {
3
+ parse(rawHistory: string): GitCommit[];
4
+ parseStream(chunks: AsyncIterable<string>): Promise<GitCommit[]>;
5
+ private parseLines;
6
+ private processLine;
7
+ }
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=GitHistoryStats.js.map
@@ -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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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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