Table of Contents
以前、白鍵のみのシンプルなキーボードアプリをAudioKitで作成しました。
今回はこのアプリを改良して、以下の機能を加えてみます。
- 黒鍵
- 音の種類(サイン波、矩形波、三角波、ノコギリ波)を変更
- 音量(波の振幅)の変更
アプリの完成例
以下のように、鍵盤に黒鍵を加え、さらに音の波形の種類を選択するためのピッカーと音の振幅を変更するためのスライダーが追加します。
これらのUIパーツの使い方は、以下の記事を御覧ください。
アプリのソースコード
アプリの全ソースコードです。
必要な外部フレームワークは AudioKit のみです。
ViewController.swift
import UIKit import AudioKit class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource { let statusBarHeight = UIApplication.shared.statusBarFrame.height var oscillator: AKOscillator! var waveformPlot: AKNodeOutputPlot! var amplitudeLabel: UILabel! var currentAmplitude: Double = 1.0 // 音の周波数 let whiteFrequencies:[Double] = [ 523.23,// C 587.34,// D 659.25,// E 698.45,// F 783.98,// G 879.99,// A 987.75,// B 1046.50// C ] let blackFrequencies:[Double] = [ 554.36,// C# 622.25,// D# 739.98,// F# 830.60,// G# 932.32,// A# ] // 波形の種類 let waveType = [ "sine": AKTable(.sine), "sawtooth": AKTable(.sawtooth), "square": AKTable(.square), "triangle": AKTable(.triangle) ] override func viewDidLoad() { super.viewDidLoad() // 波形の種類を設定 oscillator = AKOscillator(waveform: waveType["sine"]!) AudioKit.output = oscillator AudioKit.start() self.createWaveViewer(oscillator) self.createSoundTypeSelector() self.createVolumeSlider() self.createKeyboard() } func createWaveViewer(_ oscillator: AKOscillator) { // 鳴っている音の波形ビューを作成 if waveformPlot != nil { waveformPlot.removeFromSuperview() } waveformPlot = AKNodeOutputPlot(oscillator, frame: CGRect( x: 0, y: statusBarHeight, width: self.view.frame.width, height: 100) ) waveformPlot.plotType = .buffer self.view.addSubview(waveformPlot) } func createSoundTypeSelector() { // 音色を選択するピッカー let picker = UIPickerView(frame: CGRect(x: 0, y: statusBarHeight + 100, width: self.view.frame.width, height: 100)) picker.delegate = self picker.dataSource = self picker.selectRow(0, inComponent: 0, animated: true) self.view.addSubview(picker) } func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return waveType.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return [String](waveType.keys)[row] } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { let sound = [AKTable](waveType.values)[row]; self.updateWaveform(sound) } // 音色の更新 func updateWaveform(_ sound: AKTable) { AudioKit.stop() oscillator = AKOscillator(waveform: sound) self.createWaveViewer(oscillator) AudioKit.output = oscillator AudioKit.start() } func createVolumeSlider() { // 音量(振幅)を変更するスライダーを作成 let slider = UISlider() slider.frame.size.width = 200 slider.center = self.view.center slider.sizeToFit() slider.minimumValue = 0 slider.maximumValue = 100 slider.value = 100 slider.addTarget(self, action: #selector(self.changeAmplitude), for: .valueChanged) self.view.addSubview(slider) amplitudeLabel = UILabel() amplitudeLabel.text = "\(slider.value)%" amplitudeLabel.font = UIFont(name: "Arial", size: 24) amplitudeLabel.sizeToFit() amplitudeLabel.center.x = self.view.center.x amplitudeLabel.center.y = self.view.center.y - 24 self.view.addSubview(amplitudeLabel) } func changeAmplitude(_ sender: UISlider) { let value = sender.value currentAmplitude = value / 100 amplitudeLabel.text = "\(value.rounded())%" } func createKeyboard() { // 鍵盤の作成 let maxWhiteKeyLength = whiteFrequencies.count let maxBlackKeyLength = blackFrequencies.count let keyWidth: CGFloat = self.view.frame.width / CGFloat(maxWhiteKeyLength) let keyHeight: CGFloat = 160 let blackKeyWidth = keyWidth * 0.8 let blackKeyHeight = keyHeight * 0.5 // 白鍵の作成 for i in 0 ..< maxWhiteKeyLength { let button = UIButton(frame: CGRect( x: CGFloat(i) * keyWidth, y: self.view.frame.height - keyHeight, width: keyWidth, height: keyHeight) ) button.backgroundColor = UIColor(white: 1, alpha: 1) button.layer.borderWidth = 1 button.layer.borderColor = UIColor.black.cgColor button.layer.cornerRadius = 4 button.setTitleColor(UIColor.blue, for: .normal) button.addTarget(self, action: #selector(self.toggleSound), for: .touchDown) button.addTarget(self, action: #selector(self.toggleSound), for: .touchUpInside) button.layer.setValue(whiteFrequencies[i], forKey: "freq") self.view.addSubview(button) } // 黒鍵の作成 for i in 0 ..< maxBlackKeyLength { let button = UIButton(frame: CGRect( x: keyWidth / 2 + CGFloat(i) * keyWidth + keyWidth * 0.1, y: self.view.frame.height - keyHeight, width: blackKeyWidth, height: blackKeyHeight) ) if i >= 2 { button.center.x += keyWidth } button.backgroundColor = UIColor(white: 0, alpha: 1) button.layer.borderWidth = 1 button.layer.borderColor = UIColor.black.cgColor button.layer.cornerRadius = 4 button.setTitleColor(UIColor.blue, for: .normal) button.addTarget(self, action: #selector(self.toggleSound), for: .touchDown) button.addTarget(self, action: #selector(self.toggleSound), for: .touchUpInside) button.layer.setValue(blackFrequencies[i], forKey: "freq") self.view.addSubview(button) } } func toggleSound(_ sender: UIButton) { if oscillator.isPlaying { oscillator.stop() } else { oscillator.amplitude = currentAmplitude oscillator.frequency = sender.layer.value(forKey: "freq") as! Double oscillator.start() } } }
まだ、鍵盤の同時押しに対応できていなかったり、細かい動作上の不具合がありますが、キーボードアプリの基本形が出来てきました。