Created
November 24, 2020 12:23
-
-
Save vinimonteiro/8ffad10a32e89ac55077fcb88af4e311 to your computer and use it in GitHub Desktop.
Data sonification service controller
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
package com.logmonitor.controller; | |
import java.io.BufferedReader; | |
import java.io.ByteArrayOutputStream; | |
import java.io.FileReader; | |
import java.io.IOException; | |
import java.util.Base64; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.TimeUnit; | |
import org.jfugue.midi.MidiFileManager; | |
import org.jfugue.pattern.Pattern; | |
import org.jfugue.player.Player; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RequestMethod; | |
import org.springframework.web.bind.annotation.RestController; | |
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; | |
import com.logmonitor.utils.Const; | |
@RestController | |
@RequestMapping("/") | |
public class SonificationController { | |
private static final String EXCEPTION_SOUND = "C5maj7w "; | |
private static final String TIMEOUT_SOUND = "V9 [Hand_Clap]q+[Crash_Cymbal_1]q"; | |
private static final Logger logger = LoggerFactory.getLogger(SonificationController.class); | |
private static ExecutorService executorService = Executors.newSingleThreadExecutor(); | |
Player player = new Player(); | |
Player playerPiano = new Player(); | |
private Object lock = new Object(); | |
/* | |
* It reads from file infinitely. If line is empty / file has finished, it waits 500ms and tries again as it is inside while true block. | |
* The file contains response times and whether an exception or timeout occurred. | |
* Such rule is just for demonstration purposes, a real log file would contain other things of course. | |
* But basically one would parse it to have in the end: response times and other events of interest, like exceptions and timeouts. | |
* For each line in the file, it gets correspondent sound based on pre-defined rules. | |
* This can also vary, each one can define it's own rules. | |
* I'm far from a musician, I was not very creative for sure. I only set for high response times to play a high note, and low response times to play low note. | |
* And for exception and timeout I defined to play some note chosen mostly arbitrarily, just wanted a sound to call for attention. | |
*/ | |
@RequestMapping(value = "/application/{appId}/sonify", method = RequestMethod.GET) | |
public SseEmitter sonifyData(@PathVariable("appId") String appId) { | |
final SseEmitter sseEmitter = new SseEmitter(); | |
executorService.submit(() -> { | |
ByteArrayOutputStream baos = null; | |
try (BufferedReader br = new BufferedReader(new FileReader("/logs/"+appId+".log"))) { | |
while(true){ | |
String line = br.readLine(); | |
if(line==null){ | |
synchronized (lock){ | |
lock.wait(500); | |
} | |
}else{ | |
TimeUnit.MILLISECONDS.sleep(500); | |
baos = new ByteArrayOutputStream(); | |
if(Const.TIMEOUT.equals(line)){ | |
MidiFileManager.save(player.getSequence(new Pattern(TIMEOUT_SOUND)), baos); | |
TimeUnit.MILLISECONDS.sleep(2000); | |
sseEmitter.send("base64="+encoder(baos.toByteArray())); | |
}else{ | |
MidiFileManager.save(playerPiano.getSequence(new Pattern(getNote(line))), baos); | |
sseEmitter.send("base64="+encoder(baos.toByteArray())); | |
} | |
sseEmitter.send(line); | |
} | |
} | |
} catch (IOException | InterruptedException e) { | |
logger.error(e.getMessage()); | |
}finally { | |
sseEmitter.complete(); | |
} | |
}); | |
return sseEmitter; | |
} | |
private static String encoder(byte[] byteArray) { | |
return Base64.getEncoder().encodeToString(byteArray); | |
} | |
private String getNote(String line) throws IOException, InterruptedException{ | |
if(Const.EXCEPTION.equals(line)){ | |
return EXCEPTION_SOUND; | |
}else{ | |
return (int)normalizeToRange(Double.valueOf(line))+"q "; | |
} | |
} | |
/* | |
* Normalizes value to a range. This range is the min and max the note can be in a piano. | |
*/ | |
private static double normalizeToRange(double value) { | |
return ((value - Const.DATA_MIN) / (Const.DATA_MAX - Const.DATA_MIN)) | |
* (Const.NORMALIZED_RANGE_MAX - Const.NORMALIZED_RANGE_MIN) + Const.NORMALIZED_RANGE_MIN; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment