package javaforce.media; /** * Music player. * * Plays tracked music. * Although similar to MOD type files it is not compatible with any and conversion would be difficult. * Also supports adding sound effects during playback. * * @author pquiring */ import java.util.*; import java.io.*; import javaforce.*; public class Music { public static final int VERSION = 1; public static interface Listener { /** Triggered when the song ends (even if repeating) */ public void musicEnded(); /** Triggered each time a chunk of samples is going to be played. Use to apply effects, etc.*/ public void musicSamples(short samples[]); /** Triggered as the music moves to each row. */ public void musicRow(int sequence, int pattern, int row); } private enum Play {song, pattern, note}; public static class Version implements Serializable { public static final long serialVersionUID = 1L; public int version = VERSION; } public static class Track implements Serializable { public static final long serialVersionUID = 1L; public byte startInstrument; public float startVolL, startVolR; //0.0f - 1.0f : full vol = max(L,R) public float startVolVibrateSpeed; //speed for vibrate cmds public float startPanVibrateSpeed; //speed for vibrate cmds //use vol public float startFreqVibrateSpeed; //speed for vibrate cmds public byte notes[] = new byte[64]; //MIDI : 0=C-2 ... 0x7f = ??? (-1 = no note) public byte volcmds[] = new byte[64]; public int volparams[] = new int[64]; //or float public byte fxcmds[] = new byte[64]; public int fxparams[] = new int[64]; //or float public int flags; //not used public transient float sIdx; //sample index public transient float freq; public transient float freqPow2; public transient boolean playing; public transient Instrument i; //instrument public transient Region r; //region public transient Sample s; //sample public transient int volcmd; //current fx public transient float volparam; //current param public transient int fxcmd; //current fx public transient float fxparam; //current param public transient float volL, volR; //volumes public transient float vol; //volume public transient boolean loop; //holding note (looping) public transient boolean sustain; //looping in sustain public transient float volVibrateSpeed, panVibrateSpeed, freqVibrateSpeed; public transient float port; //port up/down public transient float lastFreq, targetFreq; //port to note public transient int on, off; //tremor public transient boolean on_off; //tremor public transient int on_off_cnt; //tremor public transient boolean mute; public transient int delay; public transient float volSlide, panSlide; public transient float volVibratePos; public transient float volVibrateDir; public transient float volVibrateMag; public transient float panVibratePos; public transient float panVibrateDir; public transient float panVibrateMag; public transient float freqVibratePos; public transient float freqVibrateDir; public transient float freqVibrateMag; public transient boolean interpolate; //linear interpolation (see http://en.wikipedia.org/wiki/Interpolation) public transient float attenuation; public Track() { //set default values for(int n=0;n<64;n++) { notes[n] = -1; } startVolL = 1.0f; startVolR = 1.0f; startVolVibrateSpeed = 1.0f / 44100.0f * 0.25f; startPanVibrateSpeed = 1.0f / 44100.0f * 0.25f; startFreqVibrateSpeed = 1.0f / 44100.0f * 0.25f; } public void reset(Music music) { sIdx = 0.0f; freq = -0.0f; //negative zero denotes no current note playing = false; i = music.song.instruments.get(startInstrument); volL = startVolL; volR = startVolR; volVibrateSpeed = startVolVibrateSpeed; panVibrateSpeed = startPanVibrateSpeed; freqVibrateSpeed = startFreqVibrateSpeed; nextNote(music); } public void nextNote(Music music) { lastFreq = freq; int idx = music.rowIdx; int note = notes[idx]; if (note != -1) { sIdx = 0.0f; interpolate = note % 12 != 0; playing = true; vol = 1.0f; r = i.getRegion(note); s = music.song.samples.get(r.sample); freq = (note - r.unity) / 12.0f; freqPow2 = (float)Math.pow(2.0, freq); sustain = s.sustainStart != -1; loop = !sustain && s.loopStart != -1; attenuation = s.attenuation; } setCmds(volcmds[idx], volparams[idx], fxcmds[idx], fxparams[idx], music); } /** Assign new commands */ public void setCmds(byte volcmd, int volparam, byte fxcmd, int fxparam, Music music) { this.volcmd = volcmd; this.volparam = volparam; mute = false; delay = 0; switch (volcmd) { case Music.VOLCMD_SET_VOLUME: vol = Float.intBitsToFloat(volparam); break; case Music.VOLCMD_SET_VOL_VIBRATE_SPEED: volVibrateSpeed = Float.intBitsToFloat(volparam); break; case Music.VOLCMD_SET_PAN_VIBRATE_SPEED: panVibrateSpeed = Float.intBitsToFloat(volparam); break; case Music.VOLCMD_TREMOLO: //vibrate vol volVibrateMag = Float.intBitsToFloat(volparam); volVibrateDir = volVibrateSpeed; volVibratePos = 0.0f; break; case Music.VOLCMD_PANBRELLO: //vibrate pan panVibrateMag = Float.intBitsToFloat(volparam); panVibrateDir = panVibrateSpeed; panVibratePos = 0.0f; break; case Music.VOLCMD_SET_PANNING: float pan = Float.intBitsToFloat(volparam); if (pan == 0.0f) { volR = 1.0f; volL = 1.0f; } else if (pan > 0.0f) { volR = 1.0f; volL = 1.0f - pan; } else { volL = 1.0f; volR = 1.0f + pan; } break; case Music.VOLCMD_SLIDE: volSlide = Float.intBitsToFloat(volparam); break; case Music.VOLCMD_PAN_SLIDE: panSlide = Float.intBitsToFloat(volparam); break; } this.fxcmd = fxcmd; this.fxparam = fxparam; switch (fxcmd) { case FXCMD_KEY_OFF: sustain = false; break; case FXCMD_CUT_OFF: playing = false; break; case FXCMD_PATTERN_BREAK: music.rowIdx = 63; break; case Music.FXCMD_PORTAMENTO_TO_NOTE: if (fxparam != 0) port = Float.intBitsToFloat(fxparam); targetFreq = freq; freq = lastFreq; break; case Music.FXCMD_SET_VIBRATE_SPEED: freqVibrateSpeed = Float.intBitsToFloat(fxparam); break; case Music.FXCMD_VIBRATO: freqVibrateMag = Float.intBitsToFloat(fxparam); freqVibrateDir = freqVibrateSpeed; freqVibratePos = 0.0f; break; case Music.FXCMD_PORTAMENTO: if (fxparam != 0) port = Float.intBitsToFloat(fxparam); break; case Music.FXCMD_TREMOR: if (fxparam != 0) { on = fxparam >>> 16; if (on == 0) on++; off = fxparam & 0xffff; if (off == 0) off++; on_off = true; on_off_cnt = 0; } break; case Music.FXCMD_SET_INSTRUMENT: i = music.song.instruments.get(fxparam); break; case Music.FXCMD_DELAY_START: delay = fxparam; break; case Music.FXCMD_SAMPLE_OFFSET: sIdx = fxparam; break; case Music.FXCMD_SET_BPM: music.samplesPerBeat = 44100 * 60 / fxparam; music.samplesThisBeat = 0; break; } } /** Process commands (per sample) */ public void doCmds() { switch (volcmd) { case Music.VOLCMD_TREMOLO: if (volVibrateDir > 0.0f) { vol += volVibrateMag; } else { vol -= volVibrateMag; } if (vol > 1.0f) vol = 1.0f; if (vol < 0.0f) vol = 0.0f; volVibratePos += volVibrateDir; if (volVibratePos > 1.0f || volVibratePos < 0.0f) { volVibrateDir *= -1.0f; } break; case Music.VOLCMD_PANBRELLO: if (panVibrateDir > 0.0f) { volR += panVibrateMag; volL -= panVibrateMag; } else { volR -= panVibrateMag; volL += panVibrateMag; } if (volR > 1.0f) volR = 1.0f; if (volR < 0.0f) volR = 0.0f; if (volL > 1.0f) volL = 1.0f; if (volL < 0.0f) volL = 0.0f; panVibratePos += panVibrateDir; if (panVibratePos > 1.0f || panVibratePos < 0.0f) { panVibrateDir *= -1.0f; } break; case Music.VOLCMD_SLIDE: vol += volSlide; if (vol > 1.0f) vol = 1.0f; if (vol < 0.0f) vol = 0.0f; break; case Music.VOLCMD_PAN_SLIDE: volR += panSlide; if (volR > 1.0f) volR = 1.0f; if (volR < 0.0f) volR = 0.0f; volL -= panSlide; if (volL > 1.0f) volL = 1.0f; if (volL < 0.0f) volL = 0.0f; break; } switch (fxcmd) { case Music.FXCMD_PORTAMENTO_TO_NOTE: if (freq != targetFreq) { if (freq < targetFreq) { freq += port; if (freq > targetFreq) freq = targetFreq; } else { freq -= port; if (freq < targetFreq) freq = targetFreq; } freqPow2 = (float)Math.pow(2.0, freq); interpolate = true; } break; case Music.FXCMD_VIBRATO: if (freqVibrateDir > 0.0f) { freq += freqVibrateMag; } else { freq -= freqVibrateMag; } freqPow2 = (float)Math.pow(2.0, freq); interpolate = true; freqVibratePos += freqVibrateDir; if (freqVibratePos > 1.0f || freqVibratePos < 0.0f) { freqVibrateDir *= -1.0f; } break; case Music.FXCMD_PORTAMENTO: freq += port; freqPow2 = (float)Math.pow(2.0, freq); interpolate = true; break; case Music.FXCMD_TREMOR: mute = on_off; on_off_cnt++; if (on_off && on_off_cnt == on) { on_off = false; on_off_cnt = 0; } else if (!on_off && on_off_cnt == off) { on_off = true; on_off_cnt = 0; } break; } } } public static class Pattern implements Serializable { public static final long serialVersionUID = 1L; public String name; //stanza name (verse, chorus, etc.) public int bpm = 80; public int flags; //not used public ArrayList<Track> tracks = new ArrayList<Track>(); public void reset(Music music) { music.samplesPerBeat = 44100 * 60 / bpm; music.samplesThisBeat = 0; music.rowIdx = 0; int nTracks = tracks.size(); for(int a=0;a<nTracks;a++) { tracks.get(a).reset(music); } } public void addTracks(int cnt) { for(int a=0;a<cnt;a++) { tracks.add(new Track()); } } } /** Each audio sample for an Instrument must be 16bit, 44100Hz, mono */ public static class Instrument implements Serializable { public static final long serialVersionUID = 1L; public String name; public int flags; //not used public ArrayList<Region> regions = new ArrayList<Region>(); public Region getRegion(int note) { for(int a=0;a<regions.size();a++) { Region r = regions.get(a); if (note >= r.low && note <= r.high) { return r; } } return null; } } public static class Region implements Serializable { public static final long serialVersionUID = 1L; public int low, high, unity; public int fineTune; //not currently used public int flags; //not used public int sample; } public static class Sample implements Serializable { public static final long serialVersionUID = 1L; public String name; public int loopStart, loopEnd; public int sustainStart, sustainEnd; public float attenuation; //volume drop per sample (recommend: 0.00001f thru 0.00005f) public int flags; //not used public short samples[]; public Sample() { loopStart = loopEnd = -1; sustainStart = sustainEnd = -1; } } public static class Song implements Serializable { public static final long serialVersionUID = 1L; public String name, comment; public int flags; public ArrayList<Pattern> patterns = new ArrayList<Pattern>(); public ArrayList<Integer> sequence = new ArrayList<Integer>(); //Integer = index into patterns public ArrayList<Instrument> instruments = new ArrayList<Instrument>(); public ArrayList<Sample> samples = new ArrayList<Sample>(); } private Track sounds[] = new Track[0]; public Song song; private boolean playing; //music ready? private Object lock = new Object(); private Play play; public static final byte NOTE_NONE = -1; public static final byte VOLCMD_NONE = 0; public static final byte VOLCMD_SET_VOLUME = 1; //float+ = 0.0 - 1.0 public static final byte VOLCMD_SET_PANNING = 2; //float-+ = -1.0 - 1.0 public static final byte VOLCMD_SLIDE = 3; //float-+ public static final byte VOLCMD_PAN_SLIDE = 4; //float-+ public static final byte VOLCMD_SET_VOL_VIBRATE_SPEED = 5; //float+ = sets vibrate Hz (default defined in track) public static final byte VOLCMD_SET_PAN_VIBRATE_SPEED = 6; //float+ = sets vibrate Hz (default defined in track) public static final byte VOLCMD_TREMOLO = 7; //float+ = vibrate volume (sets distance) public static final byte VOLCMD_PANBRELLO = 8; //float+ = vibrate panning public static final byte VOLCMD_MASTER_FLAG = (byte)0x80; //same as all above except effects ALL tracks public static final byte FXCMD_NONE = 0; public static final byte FXCMD_PORTAMENTO = 1; //float-+ = pitch slide up (per sample) public static final byte FXCMD_PORTAMENTO_TO_NOTE = 2; //float+ = change pitch from last note to this one public static final byte FXCMD_SET_VIBRATE_SPEED = 3; //float+ = sets vibrate Hz (default defined in track) public static final byte FXCMD_VIBRATO = 4; //float+ = vibrate pitch public static final byte FXCMD_TREMOR = 5; //short/short = turns sample on/off rapidly (short = time in samples) public static final byte FXCMD_PATTERN_BREAK = 6; //no param = end pattern public static final byte FXCMD_KEY_OFF = 7; //no param = end sustain (may start looping if defined) public static final byte FXCMD_CUT_OFF = 8; //no param = end note public static final byte FXCMD_SET_INSTRUMENT = 9; //int = change instrument public static final byte FXCMD_DELAY_START = 10; //int = samples delay public static final byte FXCMD_SAMPLE_OFFSET = 11; //int = samples offset public static final byte FXCMD_SET_BPM = 12; //int = new bpm private Listener listener; private AudioOutput output; private int samplesPerBuffer, samplesPerBuffer2; private short[] samples; private Timer timer; //sound channel stuff (not related to music) private static ArrayList<Instrument> loadedSounds = new ArrayList<Instrument>(); private static ArrayList<Sample> loadedSoundsSamples = new ArrayList<Sample>(); private int seqIdx = 0; //current seq # private int patternIdx = 0; //current pattern private Pattern pattern; //current pattern private boolean repeatSong; //repeat song continuously private float mL = 1.0f, mR = 1.0f; //overall music volume level private float sL = 1.0f, sR = 1.0f; //overall sound volume level private int samplesPerBeat; private int samplesThisBeat; private int rowIdx; //0-63 public static final float octave12[] = new float[12]; //freq for each note in one octave (12 steps) static { float step = 1.0f / 12.0f; float value = 1.0f; for(int a=0;a<12;a++) { octave12[a] = value; value += step; } } /** Starts the sound output engine. * After you can start music or sound playback. */ public boolean start(int milliSec, int soundChannels) { if (output != null) stop(); output = new AudioOutput(); if (output == null) return false; sounds = new Track[soundChannels]; for(int a=0;a<soundChannels;a++) { sounds[a] = new Track(); } samplesPerBuffer = 44100 * 2 * milliSec / 1000; //*2 stereo samplesPerBuffer2 = samplesPerBuffer / 2; samples = new short[samplesPerBuffer]; if (!output.start(2, 44100, 16, samplesPerBuffer * 2, null)) { //*2 = bytes JFLog.log("Music.start() failed"); return false; } timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() {public void run() {process();}}, milliSec / 2, milliSec); output.write(samples); return true; } /** Stops the sound output engine, which stops music and sound output. */ public void stop() { if (timer != null) { timer.cancel(); timer = null; } if (output != null) { output.stop(); output = null; } } public boolean load(String fn) { //load a .mproj file close(); try { FileInputStream fis = new FileInputStream(fn); ObjectInputStream ois = new ObjectInputStream(fis); Version version = (Version)ois.readObject(); song = (Song)ois.readObject(); if (song.samples == null) song.samples = new ArrayList<Sample>(); //beta upgrade fis.close(); return true; } catch (Exception e) { JFLog.log(e); return false; } } public boolean save(String fn) { //save a .mproj file try { FileOutputStream fos = new FileOutputStream(fn); ObjectOutputStream oos = new ObjectOutputStream(fos); song.flags = 0; oos.writeObject(new Version()); oos.writeObject(song); fos.close(); return true; } catch (Exception e) { JFLog.log(e); return false; } } public void close() { synchronized(lock) { playing = false; } song = null; } public void reset() { close(); song = new Song(); Pattern pattern = new Pattern(); pattern.name = "Verse 1"; pattern.bpm = 80; pattern.addTracks(4); song.patterns.add(pattern); } public void replay() { synchronized(lock) { playing = false; } seqIdx = 0; rowIdx = 0; if (!prepNextPattern()) return; playing = true; } public void setListener(Listener listener) { this.listener = listener; } public void playSong(boolean repeat) { if (playing) { JFLog.log("Music:error:playSong:already playing"); return; } this.repeatSong = repeat; play = Play.song; seqIdx = 0; rowIdx = 0; if (!prepNextPattern()) return; if (listener != null) listener.musicRow(seqIdx, patternIdx, rowIdx); playing = true; } public void playPattern(int patternIdx) { if (playing) { JFLog.log("Music:error:playPattern:already playing"); return; } play = Play.pattern; seqIdx = -1; this.patternIdx = patternIdx; rowIdx = 0; pattern = song.patterns.get(patternIdx); pattern.reset(this); if (listener != null) listener.musicRow(seqIdx, patternIdx, rowIdx); playing = true; } public void playRow(int patternIdx, int rowIdx) { if (playing) return; play = Play.note; seqIdx = -1; this.patternIdx = patternIdx; this.rowIdx = rowIdx; pattern = song.patterns.get(patternIdx); pattern.reset(this); playing = true; } /** Stops current music (but sounds continue to play) */ public void stopMusic() { synchronized(lock) { playing = false; } } public boolean isRunning() { return output != null; } public boolean isPlaying() { return playing; } /** Plays one note of an instrument thru a sound channel, returns idx of sound channel used. * @param idx = instrument index * @param LR = left/right volume (0.0 - 1.0) * @param note = note to play (midi value) * @return idx to use with other Sound channel functions, -1 if can't play now */ public synchronized int instrumentPlay(int idx, float L, float R, int note) { for(int a=0;a<sounds.length;a++) { if (!sounds[a].playing) { Track t = sounds[a]; t.volL = L; t.volR = R; t.vol = 1.0f; t.i = song.instruments.get(idx); t.r = t.i.getRegion(note); t.s = song.samples.get(t.r.sample); t.freq = (note - t.r.unity) / 12.0f; t.freqPow2 = (float)Math.pow(2.0, t.freq); t.interpolate = t.freq % 1.0f != 0.0f; t.sIdx = 0; t.sustain = t.s.sustainStart != -1; t.loop = t.s.loopStart != -1 && t.s.sustainStart == -1; t.playing = true; return a; } } return -1; } /** Plays sample thru a sound channel, returns idx of sound channel used. * @param idx = instrument index * @param LR = left/right volume (0.0 - 1.0) * @param note = note to play (midi value) * @return idx to use with other Sound channel functions, -1 if can't play now */ public synchronized int samplePlay(int idx, float L, float R) { for(int a=0;a<sounds.length;a++) { if (!sounds[a].playing) { Track t = sounds[a]; t.volL = L; t.volR = R; t.vol = 1.0f; t.i = null; t.r = null; t.s = song.samples.get(idx); t.freq = 0.0f; t.freqPow2 = (float)Math.pow(2.0, t.freq); t.interpolate = t.freq % 1.0f != 0.0f; t.sIdx = 0; t.sustain = t.s.sustainStart != -1; t.loop = t.s.loopStart != -1 && t.s.sustainStart == -1; t.playing = true; return a; } } return -1; } //Sound API /** Loads an audio file and returns its idx */ public synchronized int soundLoad(short samples[], int loopStart, int loopEnd , int sustainStart, int sustainEnd, float attenuation, int unityNote) { Instrument i = new Instrument(); i.name = ""; Region r = new Region(); i.regions.add(r); r.low = 0; r.high = 0x7f; r.unity = unityNote; r.sample = loadedSoundsSamples.size(); Sample s = new Sample(); s.samples = samples; s.loopStart = loopStart; s.loopEnd = loopEnd; s.sustainStart = sustainStart; s.sustainEnd = sustainEnd; s.attenuation = attenuation; loadedSounds.add(i); loadedSoundsSamples.add(s); return loadedSounds.size() - 1; } public synchronized int soundLoad(String fn, int loopStart, int loopEnd , int sustainStart, int sustainEnd, float attenuation) { for(int a=0;a<loadedSounds.size();a++) { if (loadedSounds.get(a).name.equals(fn)) return a; } Wav wav = new Wav(); wav.load(fn); wav.readAllSamples(); return soundLoad(wav.samples16, loopStart, loopEnd, sustainStart, sustainEnd, attenuation, 0x3c); } public void soundRemove(int idx) { loadedSounds.remove(idx); loadedSoundsSamples.remove(idx); } public void soundClear() { loadedSounds.clear(); loadedSoundsSamples.clear(); } public void soundAttenuation(int idx, float value) { loadedSoundsSamples.get(idx).attenuation = value; } /** Plays a sound, returns channel idx to modify during playback. * @param idx = sound index * @param L = left volume (0.0 - 1.0) * @param R = right volume (0.0 - 1.0) * @param note = midi note to play * @return idx to use with other Sound functions, -1 if can't play now */ public synchronized int soundPlay(int idx, float L, float R, int note) { for(int a=0;a<sounds.length;a++) { if (!sounds[a].playing) { Track t = sounds[a]; t.volL = L; t.volR = R; t.vol = 1.0f; t.i = loadedSounds.get(idx); t.r = t.i.getRegion(note); t.s = loadedSoundsSamples.get(t.r.sample); t.freq = (note - t.r.unity) / 12.0f; t.freqPow2 = (float)Math.pow(2.0, t.freq); t.interpolate = t.freq % 1.0f != 0.0f; t.sIdx = 0; t.sustain = t.s.sustainStart != -1; t.loop = t.s.loopStart != -1 && t.s.sustainStart == -1; t.attenuation = t.s.attenuation; t.playing = true; return a; } } return -1; } //API to modify a sound channel that is currently playing a sound public void channelStop(int idx) { sounds[idx].playing = false; } public void channelKeyUp(int idx) { sounds[idx].sustain = false; } public void channelAttenuation(int idx, float value) { sounds[idx].attenuation = value; } public void channelPan(int idx, float L, float R) { sounds[idx].volL = L; sounds[idx].volR = R; } public void channelFreq(int idx, float freq) { sounds[idx].freq = freq; sounds[idx].freqPow2 = (float)Math.pow(2.0, freq); sounds[idx].interpolate = freq % 1.0f != 0.0f; } public void channelApplyFX(int idx, byte fxcmd, int fxparam) { sounds[idx].setCmds((byte)0, (byte)0, fxcmd, fxparam, this); } //API to edit music public void addTrack(int pattern) { song.patterns.get(pattern).tracks.add(new Track()); } public void removeTrack(int pattern, int track) { song.patterns.get(pattern).tracks.remove(track); } public void addPattern(int nTracks) { Pattern newPattern = new Pattern(); newPattern.addTracks(nTracks); song.patterns.add(newPattern); } public void removePattern(int pattern) { song.patterns.remove(pattern); //remove all references to pattern in sequence for(int a=0;a<song.sequence.size();) { Integer seq = song.sequence.get(a); if (seq == pattern) { song.sequence.remove(a); } else { a++; } } } public boolean addSamples(String name, short samples[], int loopStart, int loopEnd , int sustainStart, int sustainEnd, float attenuation) { Sample s = new Sample(); s.name = name; s.loopStart = loopStart; s.loopEnd = loopEnd; s.sustainStart = sustainStart; s.sustainEnd = sustainEnd; s.attenuation = attenuation; s.samples = samples; song.samples.add(s); return true; } public boolean addSamples(String name, String fn, int loopStart, int loopEnd , int sustainStart, int sustainEnd, float attenuation) { Wav wav = new Wav(); if (!wav.load(fn)) return false; wav.readAllSamples(); return addSamples(name, wav.samples16, loopStart, loopEnd, sustainStart, sustainEnd, attenuation); } public void removeSamples(int idx) { song.samples.remove(idx); //patch instrument region indexes for(int a=0;a<song.instruments.size();a++) { Instrument i = song.instruments.get(a); for(int b=0;b<i.regions.size();b++) { Region r = i.regions.get(b); if (r.sample > idx) { r.sample--; } else if (r.sample == idx) { r.sample = 0; } } } } public int addInstrument(String name) { Instrument i = new Instrument(); i.name = name; song.instruments.add(i); return song.instruments.size() - 1; } public void removeInstrument(int idx) { song.instruments.remove(idx); //now cleanup ALL patterns (delete notes with this instrument and bump others above it) for(int p=0;p<song.patterns.size();p++) { Pattern pat = song.patterns.get(p); for(int t=0;t<pat.tracks.size();t++) { Track trk = pat.tracks.get(t); if (trk.startInstrument == idx) trk.startInstrument = 0; else if (trk.startInstrument > idx) trk.startInstrument--; for(int n=0;n<64;n++) { if (trk.fxcmds[n] == FXCMD_SET_INSTRUMENT) { if (trk.fxparams[n] == idx) { trk.fxcmds[n] = 0; trk.fxparams[n] = 0; } else if (trk.fxparams[n] > idx) { trk.fxparams[n]--; } } } } } } public void addRegion(int iidx, int low, int high, int unityNote, int sample) { Region r = new Region(); r.low = low; r.high = high; r.unity = unityNote; r.sample = sample; song.instruments.get(iidx).regions.add(r); } public void removeRegion(int iidx, int ridx) { song.instruments.get(iidx).regions.remove(ridx); } // volume control /** Range: 0.0f -> 1.0f */ public void setMasterMusicVolume(float l, float r) { mL = l; mR = r; } /** Range: 0.0f -> 1.0f */ public void setMasterSoundVolume(float l, float r) { sL = l; sR = r; } //private code private boolean prepNextPattern() { if (seqIdx >= song.sequence.size()) { playing = false; if (listener != null) listener.musicEnded(); return false; } patternIdx = song.sequence.get(seqIdx); pattern = song.patterns.get(patternIdx); pattern.reset(this); return true; } //output next audio chunk private void process() { // JFLog.log("process:" + System.currentTimeMillis()); try { Arrays.fill(samples, (short)0); synchronized(lock) { if (playing) processMusic(); processSound(); if (listener != null) listener.musicSamples(samples); } if (output != null) output.write(samples); } catch (Exception e) { JFLog.log(e); } } private void processMusic() { int idx = 0; for(int a=0;a<samplesPerBuffer2;a++) { float L = 0, R = 0; samplesThisBeat++; if (samplesThisBeat == samplesPerBeat) { samplesThisBeat = 0; if (play == Play.note) { playing = false; if (listener != null) listener.musicEnded(); return; } rowIdx++; if (rowIdx == 64) { rowIdx = 0; seqIdx++; if (play == Play.pattern) { playing = false; if (listener != null) listener.musicEnded(); return; } if (seqIdx == song.sequence.size()) { if (listener != null) listener.musicEnded(); if (!repeatSong) { playing = false; if (listener != null) listener.musicEnded(); return; } seqIdx = 0; } if (!prepNextPattern()) return; } else { int nTracks = pattern.tracks.size(); for(int t=0;t<nTracks;t++) { pattern.tracks.get(t).nextNote(this); } } if (listener != null) listener.musicRow(seqIdx, patternIdx, rowIdx); } int nTracks = pattern.tracks.size(); for(int b=0;b<nTracks;b++) { Track t = pattern.tracks.get(b); if (!t.playing) continue; if (t.mute) continue; if (t.delay > 0) { t.delay--; continue; } int cIdx = (int)t.sIdx; float sample; if (t.interpolate && ((cIdx + 1) < t.s.samples.length)) { float yA = t.s.samples[cIdx]; float yB = t.s.samples[cIdx + 1]; float mB = t.sIdx % 1.0f; float mA = 1.0f - mB; sample = yA * mA + yB * mB; } else { sample = t.s.samples[cIdx]; } t.sIdx += t.freqPow2; cIdx = (int)t.sIdx; t.vol -= t.attenuation; L += sample * t.volL * t.vol; R += sample * t.volR * t.vol; if (t.vol <= 0.0f) { t.playing = false; } else if (t.sustain && (cIdx >= t.s.sustainEnd)) { t.sIdx -= (t.s.sustainEnd - t.s.sustainStart); } else if (t.loop && (cIdx >= t.s.loopEnd)) { t.sIdx -= (t.s.loopEnd - t.s.loopStart); } else if (cIdx >= t.s.samples.length) { t.playing = false; } if (t.playing) { t.doCmds(); } } L *= mL; R *= mR; int iL = (int)L, iR = (int)R; //clamp to short if (iL < -32768) iL = -32768; if (iL > 32767) iL = 32767; if (iR < -32768) iR = -32768; if (iR > 32767) iR = 32767; samples[idx++] = (short)iL; samples[idx++] = (short)iR; } } private void processSound() { int idx = 0; for(int b=0;b<samplesPerBuffer2;b++) { float L = 0.0f; float R = 0.0f; for(int a=0;a<sounds.length;a++) { Track t = sounds[a]; if (!t.playing) continue; if (t.mute) continue; if (t.delay > 0) { t.delay--; continue; } int cIdx = (int)t.sIdx; float sample; if (t.interpolate && ((cIdx + 1) < t.s.samples.length)) { float yA = t.s.samples[cIdx]; float yB = t.s.samples[cIdx + 1]; float mB = t.sIdx % 1.0f; float mA = 1.0f - mB; sample = yA * mA + yB * mB; } else { sample = t.s.samples[cIdx]; } t.sIdx += t.freqPow2; cIdx = (int)t.sIdx; t.vol -= t.attenuation; L += sample * t.volL * t.vol; R += sample * t.volR * t.vol; if (t.vol <= 0.0f) { t.playing = false; } else if (t.sustain && (cIdx >= t.s.sustainEnd)) { t.sIdx -= (t.s.sustainEnd - t.s.sustainStart); } else if (t.loop && (cIdx >= t.s.loopEnd)) { t.sIdx -= (t.s.loopEnd - t.s.loopStart); } else if (cIdx >= t.s.samples.length) { t.playing = false; } if (t.playing) { t.doCmds(); } } L *= sL; R *= sR; int iL = (int)L, iR = (int)R; iL += samples[idx]; iR += samples[idx+1]; //clamp to short if (iL < -32768) iL = -32768; if (iL > 32767) iL = 32767; if (iR < -32768) iR = -32768; if (iR > 32767) iR = 32767; samples[idx++] = (short)iL; samples[idx++] = (short)iR; } } }