/// <summary> /// 현재 음악 테마가 바뀔 때마다 호출되어야 합니다. /// SFXTheme.CurrentSFXTheme이 null이면 아무 일도 일어나지 않습니다. /// </summary> public static void ThemeChanged() { if (SFXTheme.CurrentSFXTheme == null) { return; } void ChangeTheme(object[] args) { Score.InitializePlaylist(); for (int i = 0; i <= 8; i++) { StopPlaying(i); } foreach (KeyValuePair <int, SFXTheme.InstrumentInfo> p in SFXTheme.CurrentSFXTheme.Instruments) { try { if (p.Key == 7 || p.Key == 8) { if (p.Value.instrumentCode == -1) { if (SFXTheme.CurrentSFXTheme.Instruments.ContainsKey(1)) { //outDevice.Send(new ChannelMessage(ChannelCommand.ProgramChange, p.Key, SFXTheme.CurrentSFXTheme.Instruments[1].instrumentCode)); syn.ProgramChange(p.Key, SFXTheme.CurrentSFXTheme.Instruments[1].instrumentCode); } else { //outDevice.Send(new ChannelMessage(ChannelCommand.ProgramChange, p.Key, p.Value.instrumentCode)); syn.ProgramChange(p.Key, p.Value.instrumentCode); } } else { //outDevice.Send(new ChannelMessage(ChannelCommand.ProgramChange, p.Key, p.Value.instrumentCode)); syn.ProgramChange(p.Key, p.Value.instrumentCode); } } else { //outDevice.Send(new ChannelMessage(ChannelCommand.ProgramChange, p.Key, p.Value.instrumentCode)); syn.ProgramChange(p.Key, p.Value.instrumentCode); } } catch (ObjectDisposedException) { } //catch (OutputDeviceException) { } } chord = new Chord(SFXTheme.CurrentSFXTheme.ChordTransition); tickNumber = 0; ResetAccompaniment(); } Util.TaskQueue.Add("play", ChangeTheme); }
public void Send(byte[] msg, int offset, int length, long timestamp) { if (synth == null) { throw new InvalidOperationException("The MIDI output is not open."); } int ch = msg[offset] & 0x0F; switch (msg [offset] & 0xF0) { case 0x80: synth.NoteOff(ch, msg [offset + 1]); break; case 0x90: if (msg [offset + 2] == 0) { synth.NoteOff(ch, msg [offset + 1]); } else { synth.NoteOn(ch, msg [offset + 1], msg [offset + 2]); } break; case 0xA0: synth.KeyPressure(ch, msg [offset + 1], msg [offset + 2]); break; case 0xB0: synth.CC(ch, msg [offset + 1], msg [offset + 2]); break; case 0xC0: synth.ProgramChange(ch, msg [offset + 1]); break; case 0xD0: synth.ChannelPressure(ch, msg [offset + 1]); break; case 0xE0: synth.PitchBend(ch, msg [offset + 1] + msg [offset + 2] * 0x80); break; case 0xF0: #if NET472 || NETCOREAPP synth.Sysex(new ArraySegment <byte> (msg, offset, length), null); #else unsafe { fixed(byte *ptr = msg) synth.Sysex((IntPtr)(ptr + offset), length, IntPtr.Zero, 0); } #endif break; } }
public static void Main(string[] args) { using (var settings = new Settings()) { // Change this if you don't have pulseaudio or want to change to anything else. if (Environment.OSVersion.Platform == PlatformID.Unix) { settings[ConfigurationKeys.AudioDriver].StringValue = "pulseaudio"; } settings[ConfigurationKeys.SynthAudioChannels].IntValue = 2; using (var syn = new Synth(settings)) { foreach (var arg in args) { if (SoundFont.IsSoundFont(arg)) { syn.LoadSoundFont(arg, true); } } if (syn.FontCount == 0) { syn.LoadSoundFont("/usr/share/sounds/sf2/FluidR3_GM.sf2", true); } for (int i = 0; i < 16; i++) { syn.SoundFontSelect(i, 0); } var files = args.Where(SoundFont.IsMidiFile); if (files.Any()) { foreach (var arg in files) { using (var player = new Player(syn)) { using (var adriver = new AudioDriver(syn.Settings, syn)) { player.Add(arg); player.Play(); player.Join(); } } } } else { using (var adriver = new AudioDriver(syn.Settings, syn)) { syn.ProgramChange(0, 1); syn.NoteOn(0, 60, 120); Thread.Sleep(5000); syn.NoteOff(0, 60); } } } } }
private void channelsGrid_CellEditFinishing(object sender, BrightIdeasSoftware.CellEditEventArgs e) { if (e.Column.AspectName == "instrumentName") { (e.RowObject as midiTrack).instrument = (e.Control as ComboBox).SelectedIndex; if (player != null && player.Status == FluidPlayerStatus.Playing) { syn.ProgramChange((e.RowObject as midiTrack).channel - 1, (e.Control as ComboBox).SelectedIndex); } } }
public void Send(byte [] msg, int offset, int length, long timestamp) { if (synth == null) { throw new InvalidOperationException("The MIDI output is not open."); } int ch = msg [offset] & 0x0F; switch (msg [offset] & 0xF0) { case 0x80: synth.NoteOff(ch, msg [offset + 1]); break; case 0x90: if (msg [offset + 2] == 0) { synth.NoteOff(ch, msg [offset + 1]); } else { synth.NoteOn(ch, msg [offset + 1], msg [offset + 2]); } break; case 0xA0: synth.KeyPressure(ch, msg [offset + 1], msg [offset + 2]); break; case 0xB0: synth.CC(ch, msg [offset + 1], msg [offset + 2]); break; case 0xC0: synth.ProgramChange(ch, msg [offset + 1]); break; case 0xD0: synth.ChannelPressure(ch, msg [offset + 1]); break; case 0xE0: synth.PitchBend(ch, msg [offset + 1] + msg [offset + 2] * 0x80); break; case 0xF0: synth.Sysex(new ArraySegment <byte> (msg, offset, length).ToArray(), null); break; } }
void DoSend(byte[] msg, int offset, int count, long timestamp) { #if MIDI_MANAGER output.Send(msg, offset, count, timestamp); #else // FIXME: consider timestamp. int ch = msg [offset] & 0x0F; switch (msg [offset] & 0xF0) { case 0x80: syn.NoteOff(ch, msg [offset + 1]); break; case 0x90: if (msg [offset + 2] == 0) { syn.NoteOff(ch, msg [offset + 1]); } else { syn.NoteOn(ch, msg [offset + 1], msg [offset + 2]); } break; case 0xA0: // No PAf in fluidsynth? break; case 0xB0: syn.CC(ch, msg [offset + 1], msg [offset + 2]); break; case 0xC0: syn.ProgramChange(ch, msg [offset + 1]); break; case 0xD0: syn.ChannelPressure(ch, msg [offset + 1]); break; case 0xE0: syn.PitchBend(ch, msg [offset + 1] + msg [offset + 2] * 0x80); break; case 0xF0: syn.Sysex(new ArraySegment <byte> (msg, offset, count).ToArray(), null); break; } #endif }
public async Task Play() { using (var settings = new Settings()) { settings[ConfigurationKeys.AudioDriver].StringValue = "pulseaudio"; settings[ConfigurationKeys.SynthAudioChannels].IntValue = 2; settings[ConfigurationKeys.AudioRealtimePrio].IntValue = 0; settings[ConfigurationKeys.SynthVerbose].IntValue = 0; settings[ConfigurationKeys.AudioPeriodSize].IntValue = 1024; settings[ConfigurationKeys.SynthReverbActive].IntValue = ReverbEnabled ? 1 : 0; settings[ConfigurationKeys.SynthChorusActive].IntValue = ChorusEnabled ? 1 : 0; using (var syn = new Synth(settings)) { syn.LoadSoundFont("/usr/share/sounds/sf2/FluidR3_GM.sf2", true); using (var adriver = new AudioDriver(syn.Settings, syn)) { if (ReverbEnabled) { syn.SetReverb(ReverbRoomSize, ReverbDamping, ReverbWidth, ReverbLevel); } if (ChorusEnabled) { syn.SetChorus(ChorusNumVoices, ChorusLevel, ChorusSpeed, ChorusDepthMS, ChorusMod); } // Hardcoded, will be changed in the future syn.ProgramChange(1, (int)GeneralMidi.Violin); if (Midi != null) { // Meanwhile we are cheating a little bit and using the build-in FluidSynth MIDI player // In the future it would be nice to read the events from the MIDI file and play them ourselves // That way we could mix MIDI files with our own music expressions, and maybe even transform MIDI files using (var player = new Player(syn)) { player.Add(Midi); player.Play(); // Synchronous join. Thankfully this code runs in a separate thread. Would be nice to have a async version player.Join(); } } else { var sw = new System.Diagnostics.Stopwatch(); sw.Start(); // Explaining the timer process used // All note commands (NoteOn/NoteOff) are ordered by their noteCommand.Timestamp (value in milliseconds relative to the beginning // of the music). Let's imagine that two of them are, for example, one second apart. Ideally, we would want to do a // Thread.Sleep( 1000 ) and hope that the thread would unblock exactly 1000 milliseconds after. However, the system's clock // resolution varies between different Operating Systems and different physical Processors. So we could not be sure when the thread // would be woken. Instead we do a Thread.Sleep( 1000 - minResolution ), which hopefully means that our thread will awake before the note is due // Three things can happen now: // - We can be early, i.e. we still have to wait a bit, and for that we use a simple loop with a high-precision Stopwatch // - We can be on time, in which case we just play the note // - We can be late, in wich case we play the note right away and add to the drift variable how late we are // Every subsequent timestamp will have the drift variable added to it to compensate int minResolution = 30; int drift = 0; foreach (INoteCommand noteCommand in BuildCommands(Notes)) { int timestamp = noteCommand.Timestamp + drift; int elapsed = (int)sw.Elapsed.TotalMilliseconds; if (timestamp - minResolution > elapsed) { await Task.Delay(Math.Max(0, timestamp - elapsed - minResolution)); } while (timestamp > sw.Elapsed.TotalMilliseconds) { Thread.Sleep(0); } elapsed = (int)sw.Elapsed.TotalMilliseconds; noteCommand.Apply(syn); if (timestamp < elapsed) { drift += elapsed - timestamp; } } sw.Stop(); Console.WriteLine($"Total drift: {drift}ms out of {sw.Elapsed.TotalMilliseconds}ms"); } } } } }