<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chordprogressor</title>
<script src="
https://cdn.tailwindcss.com"></script>
<link href="
https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Roboto Mono', monospace;
}
.chord-card {
transition: all 0.3s ease-in-out;
box-shadow: 0 4px 6px rgba(0,0,0,0.1), 0 1px 3px rgba(0,0,0,0.08);
}
.chord-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
}
.btn-rock {
transition: all 0.2s ease;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.btn-rock:active {
transform: translateY(2px) scale(0.98);
box-shadow: 0 2px 3px rgba(0,0,0,0.1);
}
/* Removed the background-image arrow from the select element */
select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
</style>
</head>
<body class="bg-gray-800 text-gray-100 flex flex-col items-center justify-center min-h-screen p-4">
<div class="w-full max-w-5xl text-center">
<header class="mb-8">
<h1 class="text-4xl md:text-5xl font-bold text-amber-400 mb-2 tracking-wider">Chordprogressor</h1>
<p class="text-lg text-gray-400">Finde deine nächste Song-Inspiration.</p>
</header>
<main class="bg-gray-900 rounded-2xl p-6 md

-8 shadow-2xl border border-gray-700">
<!-- Controls -->
<div class="flex flex-col md:flex-row items-center justify-center gap-4 mb-8 flex-wrap">
<div class="flex items-center gap-3">
<label for="key-select" class="text-lg font-semibold">Tonart:</label>
<select id="key-select" class="bg-gray-700 text-white border border-gray-600 rounded-lg pl-4 pr-8 py-2 focus

utline-none focus:ring-2 focus:ring-amber-400"></select>
</div>
<div class="flex items-center gap-3">
<label for="style-select" class="text-lg font-semibold">Stil:</label>
<select id="style-select" class="bg-gray-700 text-white border border-gray-600 rounded-lg pl-4 pr-8 py-2 focus

utline-none focus:ring-2 focus:ring-amber-400"></select>
</div>
<div class="flex items-center gap-3">
<label for="mode-select" class="text-lg font-semibold">Modus:</label>
<select id="mode-select" class="bg-gray-700 text-white border border-gray-600 rounded-lg pl-4 pr-8 py-2 focus

utline-none focus:ring-2 focus:ring-amber-400"></select>
</div>
<div class="flex items-center gap-3">
<label for="bars-select" class="text-lg font-semibold">Takte:</label>
<select id="bars-select" class="bg-gray-700 text-white border border-gray-600 rounded-lg pl-4 pr-8 py-2 focus

utline-none focus:ring-2 focus:ring-amber-400"></select>
</div>
<button id="generate-btn" class="btn-rock w-full lg:w-auto bg-amber-500 hover:bg-amber-600 text-gray-900 font-bold py-2 px-6 rounded-lg text-lg mt-4 md:mt-0">
Progression generieren
</button>
</div>
<!-- Display Area -->
<div id="progression-display" class="min-h-[250px] bg-gray-800 rounded-xl p-6 flex flex-col items-center justify-center text-center border-2 border-dashed border-gray-700">
<p id="initial-message" class="text-gray-500 text-lg">Wähle deine Optionen und klicke auf den Button, um zu starten!</p>
<div id="results-container" class="hidden">
<p id="progression-name" class="text-2xl font-bold text-amber-300 mb-4"></p>
<div id="chords-container" class="flex flex-wrap items-center justify-center gap-4">
<!-- Chords will be injected here -->
</div>
<p id="roman-numerals" class="text-gray-500 mt-6 text-xl tracking-widest"></p>
</div>
</div>
</main>
<footer class="mt-8 text-gray-500">
<p>© 2025 Für Songwriter gemacht.</p>
</footer>
</div>
<script>
// --- Data ---
const notes = {
sharp: ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
flat: ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']
};
const keyUseFlat = ['F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb'];
const modeInfo = {
'Ionian': { intervals: [0, 2, 4, 5, 7, 9, 11], qualities: ['maj', 'min', 'min', 'maj', 'maj', 'min', 'dim'], name: 'Ionisch (Dur)' },
'Dorian': { intervals: [0, 2, 3, 5, 7, 9, 10], qualities: ['min', 'min', 'maj', 'maj', 'min', 'dim', 'maj'], name: 'Dorisch' },
'Phrygian': { intervals: [0, 1, 3, 5, 7, 8, 10], qualities: ['min', 'maj', 'maj', 'min', 'dim', 'maj', 'min'], name: 'Phrygisch' },
'Lydian': { intervals: [0, 2, 4, 6, 7, 9, 11], qualities: ['maj', 'maj', 'min', 'dim', 'maj', 'min', 'min'], name: 'Lydisch' },
'Mixolydian':{ intervals: [0, 2, 4, 5, 7, 9, 10], qualities: ['maj', 'min', 'dim', 'maj', 'min', 'min', 'maj'], name: 'Mixolydisch' },
'Aeolian': { intervals: [0, 2, 3, 5, 7, 8, 10], qualities: ['min', 'dim', 'maj', 'min', 'min', 'maj', 'maj'], name: 'Äolisch (Moll)' },
'Locrian': { intervals: [0, 1, 3, 5, 6, 8, 10], qualities: ['dim', 'maj', 'min', 'min', 'maj', 'maj', 'min'], name: 'Lokrisch' }
};
const classicProgressions = [
{ name: "Die Pop-Punk-Hymne", roman: "I-V-vi-IV", bars: 4, styles: ['Pop', 'Rock'] },
{ name: "Die klassische 50er-Jahre", roman: "I-vi-IV-V", bars: 4, styles: ['Pop', 'Rock'] },
{ name: "Die 'Wonderwall'", roman: "vi-IV-I-V", bars: 4, styles: ['Pop', 'Rock'] },
{ name: "Die sanfte Moll-Folge", roman: "i-VI-III-VII", bars: 4, styles: ['Pop', 'Metal'] },
{ name: "Die 'Pachelbel'-Progression", roman: "I-V-vi-iii-IV-I-IV-V", bars: 8, styles: ['Pop', 'Rock'] },
{ name: "Der epische Aufbau", roman: "I-vi-V-IV", bars: 4, styles: ['Rock', 'Pop'] },
{ name: "Hard Rock Power", roman: "I-♭VII-IV", bars: 3, styles: ['Rock', 'Metal'] },
{ name: "Die 'Sweet Home Alabama'", roman: "V-IV-I", bars: 3, styles: ['Rock', 'Blues'] },
{ name: "Arena Rock Anthem", roman: "vi-V-IV", bars: 3, styles: ['Rock']},
{ name: "12-Takt-Blues", roman: "I-I-I-I-IV-IV-I-I-V-IV-I-I", bars: 12, styles: ['Blues'] },
{ name: "Moll-Blues", roman: "i-i-i-i-iv-iv-i-i-v-iv-i-i", bars: 12, styles: ['Blues'] },
{ name: "Dunkle Vorahnung", roman: "i-VI-VII-i", bars: 4, styles: ['Metal', 'Rock'] },
{ name: "Phrygischer Riff", roman: "i-♭II-i-V", bars: 4, styles: ['Metal']},
{ name: "Power Metal Galopp", roman: "vi-iv-v-i", bars: 4, styles: ['Metal']}
];
const styleRules = {
'Pop': { palette: ['I', 'IV', 'V', 'vi', 'ii', 'iii'], tonic: 'I' },
'Rock': { palette: ['I', 'IV', 'V', 'vi', '♭VII', 'iii', 'v'], tonic: 'I' },
'Blues':{ palette: ['I', 'IV', 'V'], tonic: 'I' },
'Metal':{ palette: ['i', 'VI', 'VII', 'iv', 'v', '♭II', 'III'], tonic: 'i' },
};
// --- DOM Elements ---
const keySelect = document.getElementById('key-select');
const styleSelect = document.getElementById('style-select');
const modeSelect = document.getElementById('mode-select');
const barsSelect = document.getElementById('bars-select');
const generateBtn = document.getElementById('generate-btn');
const resultsContainer = document.getElementById('results-container');
const progressionNameEl = document.getElementById('progression-name');
const chordsContainer = document.getElementById('chords-container');
const romanNumeralsEl = document.getElementById('roman-numerals');
const initialMessage = document.getElementById('initial-message');
// --- Functions ---
function populateSelectors() {
notes.flat.forEach(key => keySelect.add(new Option(key, key)));
keySelect.value = 'C';
Object.keys(styleRules).forEach(style => styleSelect.add(new Option(style, style)));
styleSelect.add(new Option('Alle', 'Alle'), styleSelect.options[0]);
styleSelect.value = 'Alle';
Object.keys(modeInfo).forEach(modeKey => modeSelect.add(new Option(modeInfo[modeKey].name, modeKey)));
modeSelect.value = 'Ionian';
barsSelect.add(new Option('Alle', 'Alle'));
for (let i = 2; i <= 12; i++) barsSelect.add(new Option(i, i));
barsSelect.value = 'Alle';
}
function getScale(rootKey, mode) {
const currentNotes = keyUseFlat.includes(rootKey) ? notes.flat : notes.sharp;
const rootIndex = currentNotes.indexOf(rootKey);
const modeDetails = modeInfo[mode];
if (rootIndex === -1 || !modeDetails) return [];
return modeDetails.intervals.map((interval, i) => {
const noteIndex = (rootIndex + interval) % 12;
const noteName = currentNotes[noteIndex];
const quality = modeDetails.qualities
;
let chordName = noteName;
if (quality === 'min') chordName += 'm';
if (quality === 'dim') chordName += 'dim';
return chordName;
});
}
function generateDynamicProgression(style, bars) {
const rules = styleRules[style] || styleRules['Rock']; // Default to Rock rules
const progression = [rules.tonic];
for (let i = 1; i < bars; i++) {
progression.push(rules.palette[Math.floor(Math.random() * rules.palette.length)]);
}
return {
name: `Dynamische ${style}-Progression`,
roman: progression.join('-'),
bars: bars,
styles: [style]
};
}
function translateProgression(romanProgression, scale, rootKey) {
const romanMap = { 'i': 0, 'I': 0, '♭ii': 1, 'ii': 1, 'II': 1, 'iii': 2, 'III': 2, 'iv': 3, 'IV': 3, 'v': 4, 'V': 4, 'vi': 5, 'VI': 5, 'vii': 6, 'VII': 6, '♭vii': 6 };
const romanNumerals = romanProgression.split('-');
const currentNotes = keyUseFlat.includes(rootKey) ? notes.flat : notes.sharp;
const rootIndex = currentNotes.indexOf(rootKey);
return romanNumerals.map(numeral => {
if (numeral.toLowerCase() === '♭vii') {
const flatSeventhNoteIndex = (rootIndex + 10) % 12;
return currentNotes[flatSeventhNoteIndex].replace('b','♭');
}
if (numeral.toLowerCase() === '♭ii') {
const flatSecondNoteIndex = (rootIndex + 1) % 12;
return currentNotes[flatSecondNoteIndex].replace('b','♭');
}
const index = romanMap[numeral.toLowerCase()];
if (index === undefined || !scale[index]) return '?';
// Adjust for major/minor numeral casing
if (numeral === numeral.toUpperCase()) { // E.g. V in a minor key
const noteName = scale[index].replace('m','').replace('dim','');
return noteName;
}
if (numeral === numeral.toLowerCase()) { // E.g. i
if (scale[index].includes('m') || scale[index].includes('dim')) {
return scale[index];
}
return scale[index] + 'm';
}
return scale[index];
});
}
function displayProgression(progression, key, mode) {
const scale = getScale(key, mode);
const chords = translateProgression(progression.roman, scale, key);
progressionNameEl.textContent = progression.name;
romanNumeralsEl.textContent = `(${modeInfo[mode].name}) ${progression.roman}`;
chordsContainer.innerHTML = '';
chords.forEach(chord => {
if (!chord) return;
const chordDiv = document.createElement('div');
chordDiv.className = 'chord-card bg-gray-700 rounded-lg w-28 h-28 flex items-center justify-center text-3xl font-bold text-amber-400';
chordDiv.textContent = chord.replace('b', '♭').replace('#', '♯');
chordsContainer.appendChild(chordDiv);
});
}
function findProgression() {
initialMessage.style.display = 'none';
resultsContainer.classList.remove('hidden');
const selectedKey = keySelect.value;
const selectedStyle = styleSelect.value;
const selectedMode = modeSelect.value;
const selectedBars = barsSelect.value;
let availableProgressions = classicProgressions;
if (selectedStyle !== 'Alle') {
availableProgressions = availableProgressions.filter(p => p.styles.includes(selectedStyle));
}
if (selectedBars !== 'Alle') {
availableProgressions = availableProgressions.filter(p => p.bars == selectedBars);
}
let progressionToDisplay;
if (availableProgressions.length > 0) {
// Found a classic progression
progressionToDisplay = availableProgressions[Math.floor(Math.random() * availableProgressions.length)];
} else {
// No classic match, generate one dynamically
const bars = selectedBars === 'Alle' ? 4 : parseInt(selectedBars);
const style = selectedStyle === 'Alle' ? 'Rock' : selectedStyle;
progressionToDisplay = generateDynamicProgression(style, bars);
}
displayProgression(progressionToDisplay, selectedKey, selectedMode);
}
// --- Event Listeners ---
generateBtn.addEventListener('click', findProgression);
// --- Initial Setup ---
window.addEventListener('load', populateSelectors);
</script>
</body>
</html>