Reputation: 945
I have this line of chord obtained from text file. For example,
String chordLine = "C G Am C";
String transposedChordLine;
Next, I need to transpose the chordLine
into a new transposedChordLine
using the class below using two parameters, a String
chord and integer increment of transpose. For example, transpose("C", 2)
will return D
.
public class Transposer{
private int inc;
private static ArrayList<String> keysSharp;
private static ArrayList<String> keysFlat;
Transposer(){
keysSharp = new ArrayList<String>(Arrays.asList("C", "C#", "D", "D#","E", "F","F#", "G","G#", "A","A#", "B"));
keysFlat = new ArrayList<String>(Arrays.asList("C", "Db", "D", "Eb","E", "F","Gb", "G","Ab", "A","Bb", "B"));
}
public String transpose(String chord,int inc){
this.inc = inc;
String newChord;
if(chord.contains("/")){
String[] split = chord.split("/");
newChord = transposeKey(split[0]) + "/" + transposeKey(split[1]);
}else
newChord = transposeKey(chord);
return newChord;
}
private String transposeKey(String key){ // C#m/D# must pass C#m or D#
String nKey, tempKey;
if(key.length()>1){
nKey = key.substring(0, 2);
}
else{ nKey = key; }
int oldIndex, newIndex;
if(key.contains("b")){
oldIndex = (keysFlat.indexOf(nKey)>-1) ? keysFlat.indexOf(nKey) : keysFlat.indexOf(similarKey(nKey));
newIndex = (oldIndex + inc + keysFlat.size())%keysFlat.size();
tempKey = keysFlat.get(newIndex);
nKey = (key.length() < 3) ? tempKey : key.replace(nKey, tempKey);
//(nKey + key.substring(nKey.length(), key.length()));
}
else if(key.contains("#")){
oldIndex = (keysSharp.indexOf(nKey)>-1) ? keysSharp.indexOf(nKey) : keysSharp.indexOf(similarKey(nKey));
newIndex = (oldIndex + inc + keysSharp.size())%keysSharp.size();
tempKey = keysSharp.get(newIndex);
nKey = (key.length() < 3) ? tempKey : key.replace(nKey, tempKey);
}
else{
nKey = nKey.substring(0, 1);
oldIndex = (keysSharp.indexOf(nKey)>-1) ? keysSharp.indexOf(nKey) : keysSharp.indexOf(similarKey(nKey));
newIndex = (oldIndex + inc + keysSharp.size())%keysSharp.size();
tempKey = keysSharp.get(newIndex);
nKey = (key.length() < 2) ? tempKey : key.replace(nKey, tempKey);
}
return nKey;
}
private String similarKey(String nKey) {
String newKey;
switch(nKey){
case "Cb":
newKey = "B";
break;
case "Fb":
newKey = "E";
break;
case "E#":
newKey = "F";
break;
case "B#":
newKey = "c";
break;
default:
newKey = null;
}
return newKey;
}
}
How do I replace the chordLine
without losing the spaces?
increment by 2 should have transposedChordLine="D A Bm D"
Here is my current attempt:
public static void main(String[] args) {
String chordLine = "C G Am C";
String transposedChordLine;
String normalize = chordLine.replaceAll("\\s+", " ");
String[] split = normalize.split(" ");
//System.out.println(normalize);
Transposer tran = new Transposer();
String[] temp = new String[split.length];
for(int i=0 ; i<split.length ; i++){
temp[i] = tran.transpose(split[i], 2);
//System.out.println(split[i]);
System.out.print(temp[i]);
}
transposedChordLine = chordLine.replaceAll([split], temp); //which is wrong
}
Upvotes: 5
Views: 352
Reputation: 20125
Given a chord line, transposer, and an increment for transposing:
String chordLine = "C G Am C";
Transposer tran = new Transposer();
int offset = 2;
To get the transposed chord line while preserving white space, you can use regular expression lookarounds to split
at whitespace boundaries, then conditionally process the resulting strings through your transposer, as follows:
String transposed = Arrays.stream(chordLine.split("((?<=\\s)|(?=\\s))")).map( // use regex to split on every whitespace boundary
str -> // for each string in the split
Character.isWhitespace(str.charAt(0)) // if the string is whitespace
? str // then keep the whitespace
: tran.transpose(str, offset) // otherwise, it's a chord, so transpose it
).collect(Collectors.joining()); // re-join all the strings together
Or if you prefer Java 7, use a StringBuilder
to build the transposed chord line as you iterate over the tokens:
StringBuilder sb = new StringBuilder();
for (String str : chordLine.split("((?<=\\s)|(?=\\s))")) {
sb.append(Character.isWhitespace(str.charAt(0)) ? str : tran.transpose(str, offset));
}
String transposed = sb.toString();
Upvotes: 0
Reputation: 100169
Here's shorter solution (I added this method to the Transposer
class):
public String transposeLine(String chordLine, int inc) {
Pattern pattern = Pattern.compile("\\S+\\s*"); // can be moved to static final field
Matcher matcher = pattern.matcher(chordLine);
StringBuffer sb = new StringBuffer();
while(matcher.find()) {
String chord = matcher.group();
String transposed = transpose(chord.trim(), inc);
matcher.appendReplacement(sb,
String.format(Locale.ENGLISH, "%-"+chord.length()+"s", transposed));
}
matcher.appendTail(sb);
return sb.toString();
}
I'm using regex matcher to create the new String. The regular expression matches the chord name along with all the spaces after that. To ensure that replacement has the same length I use String.format
and provide format string like %-XXs
where XX
is the length of the non-transposed chord with spaces. Note that if there are not enough spaces, then the resulting line become longer.
Usage:
public static void main(String[] args) {
String chordLine = "C G Am C";
System.out.println(chordLine);
for(int i=0; i<12; i++) {
String result = new Transposer().transposeLine(chordLine, i);
System.out.println(result);
}
}
Output:
C G Am C
C G Am C
C# G# A#m C#
D A Bm D
D# A# Cm D#
E B C#m E
F C Dm F
F# C# D#m F#
G D Em G
G# D# Fm G#
A E F#m A
A# F Gm A#
B F# G#m B
Upvotes: 1
Reputation: 32323
Your tokenization is unusual enough (preserving delimiters), that you probably want to do it yourself. Basically, if you see a token that matches a note, pass it to the transposer. Otherwise, pass along a space. Use a while loop to navigate along the notes. Here's the code that does just that:
private static final Transposer transposer = new Transposer();
public static void main(String[] args) {
String chordLine = "C G Am C";
String transposed = transposeChordLine(chordLine);
System.out.println(transposed);
}
private static String transposeChordLine(String chordLine) {
char[] chordLineArray = chordLine.toCharArray();
StringBuilder transposed = new StringBuilder();
StringBuilder currentToken = new StringBuilder();
int index = 0;
while(index < chordLine.length()) {
if(chordLineArray[index] == ' ') {
transposed.append(' ');
currentToken = processToken(transposed, currentToken);
} else {
currentToken.append(chordLineArray[index]);
}
index++;
}
processToken(transposed, currentToken);
return transposed.toString();
}
private static StringBuilder processToken(StringBuilder transposed,
StringBuilder currentToken) {
if(currentToken.length() > 0) {
String currentChord = currentToken.toString();
String transposedChord = transposer.transpose(currentChord, 2);
transposed.append(transposedChord);
currentToken = new StringBuilder();
}
return currentToken;
}
Note: you have some stylistic issues with your code. You can initialize your constant chord maps in the fields themselves; by doing so in the constructor, you overwrite them, doing unnecessary work and potentially causing problems, especially in multithreaded code. Just do them inline in the field declaration. It's also good to wrap these in Collections.unmodifiableList
, so they can't be changed when your code is running and therefore it makes it easier to not accidentally make a mistake.
Also, you shouldn't save the inc
variable in a field, just pass it through as an argument. This way, if you are using the same object twice, it doesn't preserve state and is therefore threadsafe. I know these things don't matter to your current program but it's good to learn these habits now. Here's the modified Transposer
class:
public class Transposer {
private static final List<String> keysSharp = Collections.unmodifiableList(Arrays.asList("C", "C#", "D", "D#", "E",
"F", "F#", "G", "G#", "A", "A#", "B"));
private static final List<String> keysFlat = Collections.unmodifiableList(Arrays.asList("C", "Db", "D", "Eb", "E",
"F", "Gb", "G", "Ab", "A", "Bb", "B"));
public String transpose(String chord, int inc) {
String newChord;
if (chord.contains("/")) {
String[] split = chord.split("/");
newChord = transposeKey(split[0], inc) + "/" + transposeKey(split[1], inc);
} else
newChord = transposeKey(chord, inc);
return newChord;
}
private String transposeKey(String key, int inc) { // C#m/D# must pass C#m or D#
String nKey, tempKey;
if (key.length() > 1) {
nKey = key.substring(0, 2);
} else {
nKey = key;
}
int oldIndex, newIndex;
if (key.contains("b")) {
oldIndex = (keysFlat.indexOf(nKey) > -1) ? keysFlat.indexOf(nKey)
: keysFlat.indexOf(similarKey(nKey));
newIndex = (oldIndex + inc + keysFlat.size()) % keysFlat.size();
tempKey = keysFlat.get(newIndex);
nKey = (key.length() < 3) ? tempKey : key.replace(nKey, tempKey);
// (nKey + key.substring(nKey.length(), key.length()));
} else if (key.contains("#")) {
oldIndex = (keysSharp.indexOf(nKey) > -1) ? keysSharp.indexOf(nKey)
: keysSharp.indexOf(similarKey(nKey));
newIndex = (oldIndex + inc + keysSharp.size()) % keysSharp.size();
tempKey = keysSharp.get(newIndex);
nKey = (key.length() < 3) ? tempKey : key.replace(nKey, tempKey);
} else {
nKey = nKey.substring(0, 1);
oldIndex = (keysSharp.indexOf(nKey) > -1) ? keysSharp.indexOf(nKey)
: keysSharp.indexOf(similarKey(nKey));
newIndex = (oldIndex + inc + keysSharp.size()) % keysSharp.size();
tempKey = keysSharp.get(newIndex);
nKey = (key.length() < 2) ? tempKey : key.replace(nKey, tempKey);
}
return nKey;
}
private String similarKey(String nKey) {
String newKey;
switch (nKey) {
case "Cb":
newKey = "B";
break;
case "Fb":
newKey = "E";
break;
case "E#":
newKey = "F";
break;
case "B#":
newKey = "c";
break;
default:
newKey = null;
}
return newKey;
}
}
Upvotes: 1