mirror of
https://github.com/opentx/opentx.git
synced 2025-07-13 11:29:51 +03:00
Switched recorder library to one custom made by Tomas Andersson.
Lots of small changes to both Speaker and Recorder.
This commit is contained in:
parent
5a5da9da32
commit
eceedefa94
16 changed files with 575 additions and 69 deletions
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Recorder. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
<ListView Name="lvSentences"
|
<ListView Name="lvSentences"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
MouseDoubleClick="lvSentences_MouseDoubleClick"
|
MouseDoubleClick="sentenceRowDblClk"
|
||||||
VerticalAlignment="Stretch" >
|
VerticalAlignment="Stretch" >
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
<ComboBox Name="cbLanguages"
|
<ComboBox Name="cbLanguages"
|
||||||
DisplayMemberPath="lName"
|
DisplayMemberPath="lName"
|
||||||
SelectedValuePath="sName"
|
SelectedValuePath="sName"
|
||||||
SelectionChanged="cbLanguages_SelectionChanged"
|
SelectionChanged="switchLanguage"
|
||||||
Width="200" Height="23" />
|
Width="200" Height="23" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
@ -86,21 +86,21 @@
|
||||||
|
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,5,0,0">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,5,0,0">
|
||||||
<Button Content="New Sentence" Width="120" Height="32" Name="buttonAddItem" Click="buttonAddItem_Click"/>
|
<Button Content="New Sentence" Width="120" Height="32" Name="buttonAddItem" Click="addSentence"/>
|
||||||
<Button Name="btnRecord" ToolTip="Start recording" Click="btnRecord_Click" Margin="20,0,0,0" >
|
<Button Name="btnRecord" ToolTip="Start recording" Click="record" Margin="20,0,0,0" >
|
||||||
<Image Height="32" Source="/OpenTXrecorder;component/record.png"/>
|
<Image Height="32" Source="/OpenTXrecorder;component/record.png"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Name="btnStop" ToolTip="Stop recording" Click="btnStop_Click" Margin="5,0,0,0" >
|
<Button Name="btnStop" ToolTip="Stop recording" Click="stop" Margin="5,0,0,0" >
|
||||||
<Image Height="32" Source="/OpenTXrecorder;component/stop.png" />
|
<Image Height="32" Source="/OpenTXrecorder;component/stop.png" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button Name="btnPlay" ToolTip="Play Sentence" Click="btnPlay_Click" Margin="30,0,0,0" >
|
<Button Name="btnPlay" ToolTip="Play Sentence" Click="play" Margin="30,0,0,0" >
|
||||||
<Image Height="32" Source="/OpenTXrecorder;component/play.png" />
|
<Image Height="32" Source="/OpenTXrecorder;component/play.png" />
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Separator Height="5" />
|
<Separator Height="5" />
|
||||||
<Label Content="RECORDING" Name="lblRecording" HorizontalAlignment="Center" Foreground="Red" FontSize="24" FontWeight="Black" Visibility="Hidden" />
|
<Label Content="RECORDING" Name="lblRecording" HorizontalAlignment="Center" Foreground="Red" FontSize="24" FontWeight="Black" Visibility="Hidden" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Image Grid.Row="0" Grid.Column="1" Name="openTXLogo" Height="48" MouseLeftButtonDown="openTXLogo_MouseLeftButtonDown" HorizontalAlignment="Right" Cursor="Hand" Margin="0,0,0,0" Source="/OpenTXrecorder;component/recorder_logo.png" Stretch="Uniform" VerticalAlignment="Bottom" />
|
<Image Grid.Row="0" Grid.Column="1" Name="openTXLogo" Height="48" MouseLeftButtonDown="about" HorizontalAlignment="Right" Cursor="Hand" Margin="0,0,0,0" Source="/OpenTXrecorder;component/recorder_logo.png" Stretch="Uniform" VerticalAlignment="Bottom" />
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Recorder. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
@ -26,19 +26,22 @@ using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using WaveLib;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
|
||||||
namespace OpenTXrecorder
|
namespace OpenTXrecorder
|
||||||
{
|
{
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
[DllImport("winmm.dll", EntryPoint = "mciSendStringA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
|
|
||||||
private static extern int mciSendString(string lpstrCommand, string lpstrReturnString, int uReturnLength, int hwndCallback);
|
|
||||||
|
|
||||||
SentenceTables tables = new SentenceTables();
|
SentenceTables tables = new SentenceTables();
|
||||||
Languages languages = new Languages();
|
|
||||||
Language curLang;
|
|
||||||
Sentences sentences = new Sentences();
|
Sentences sentences = new Sentences();
|
||||||
int added_files = 0;
|
Languages languages = new Languages();
|
||||||
|
Environment env;
|
||||||
|
|
||||||
|
WavFileWriter filewriter;
|
||||||
|
WaveInRecorder recorder;
|
||||||
|
byte[] tmparray;
|
||||||
bool isRecording = false;
|
bool isRecording = false;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
|
@ -61,7 +64,7 @@ namespace OpenTXrecorder
|
||||||
languages.Add("Swedish", "se");
|
languages.Add("Swedish", "se");
|
||||||
languages.Add("Slovak", "sk");
|
languages.Add("Slovak", "sk");
|
||||||
languages.Add("Spanish", "es");
|
languages.Add("Spanish", "es");
|
||||||
curLang = languages[0];
|
env = new Environment(languages[0].sName);
|
||||||
cbLanguages.SelectedIndex = 0; // Note: Sets current langugage -> triggers loadlanguage()
|
cbLanguages.SelectedIndex = 0; // Note: Sets current langugage -> triggers loadlanguage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,38 +75,38 @@ namespace OpenTXrecorder
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
system_strings = System.IO.File.ReadAllLines(@"SOUNDS\" + curLang.sName + @"\SYSTEM\system_sounds.txt");
|
system_strings = System.IO.File.ReadAllLines(env.systemSounds());
|
||||||
other_strings = System.IO.File.ReadAllLines(@"SOUNDS\" + curLang.sName + @"\other_sounds.txt");
|
other_strings = System.IO.File.ReadAllLines(env.otherSounds());
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
system_strings = tables.default_system_strings[tables.toInt(curLang.sName)];
|
system_strings = tables.default_system_strings[tables.toInt(env.shortLanguage)];
|
||||||
other_strings = tables.default_other_strings[tables.toInt(curLang.sName)];
|
other_strings = tables.default_other_strings[tables.toInt(env.shortLanguage)];
|
||||||
}
|
}
|
||||||
sentences.RemoveRange(0, sentences.Count);
|
sentences.Clear();
|
||||||
|
|
||||||
foreach (string str in system_strings)
|
foreach (string str in system_strings)
|
||||||
sentences.Add(str, @"SOUNDS\" + curLang.sName + @"\SYSTEM\");
|
sentences.Add(str, env.sysDir());
|
||||||
|
|
||||||
sentences.Add(@";^ System Sounds ^;", "");
|
sentences.Add(@";^ System Sounds ^;", "");
|
||||||
|
|
||||||
foreach (string str in other_strings)
|
foreach (string str in other_strings)
|
||||||
sentences.Add(str, @"SOUNDS\" + curLang.sName + @"\");
|
sentences.Add(str, env.baseDir());
|
||||||
|
|
||||||
lvSentences.Items.Refresh(); // Workaround - Two way binding is better
|
lvSentences.Items.Refresh(); // Workaround - Two way binding is better
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveLanguage()
|
private void saveLanguage()
|
||||||
{
|
{
|
||||||
System.IO.Directory.CreateDirectory(@"SOUNDS\" + curLang.sName + @"\SYSTEM");
|
System.IO.Directory.CreateDirectory(env.sysDir());
|
||||||
StreamWriter sw = File.CreateText(@"SOUNDS\" + curLang.sName + @"\SYSTEM\system_sounds.txt");
|
StreamWriter sw = File.CreateText(env.systemSounds());
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; sentences[i].fileName != ""; i++)
|
for (i = 0; sentences[i].fileName != ""; i++)
|
||||||
{
|
{
|
||||||
sw.WriteLine(sentences[i].toRaw());
|
sw.WriteLine(sentences[i].toRaw());
|
||||||
}
|
}
|
||||||
sw.Close();
|
sw.Close();
|
||||||
sw = File.CreateText(@"SOUNDS\" + curLang.sName + @"\other_sounds.txt");
|
sw = File.CreateText(env.otherSounds());
|
||||||
for (i++; i < sentences.Count; i++)
|
for (i++; i < sentences.Count; i++)
|
||||||
{
|
{
|
||||||
sw.WriteLine(sentences[i].toRaw());
|
sw.WriteLine(sentences[i].toRaw());
|
||||||
|
@ -111,15 +114,26 @@ namespace OpenTXrecorder
|
||||||
sw.Close();
|
sw.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cbLanguages_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
private void switchLanguage(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
curLang = (Language)e.AddedItems[0];
|
env = new Environment(((Language)e.AddedItems[0]).sName);
|
||||||
loadLanguage();
|
loadLanguage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lvSentences_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
|
||||||
|
private void about(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (this.lvSentences.SelectedItems.Count < 1) return;
|
AboutWindow aboutWindow = new AboutWindow();
|
||||||
|
aboutWindow.ShowDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sentenceRowDblClk(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (this.lvSentences.SelectedItems.Count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (isRecording)
|
||||||
|
stop(null, null);
|
||||||
|
|
||||||
Sentence sentence = (Sentence)this.lvSentences.SelectedItem;
|
Sentence sentence = (Sentence)this.lvSentences.SelectedItem;
|
||||||
if (sentence.fileExists)
|
if (sentence.fileExists)
|
||||||
|
@ -133,27 +147,33 @@ namespace OpenTXrecorder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openTXLogo_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
private void play(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
AboutWindow aboutWindow = new AboutWindow();
|
sentenceRowDblClk(null, null);
|
||||||
aboutWindow.ShowDialog();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buttonAddItem_Click(object sender, RoutedEventArgs e)
|
private void addSentence(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
added_files++;
|
int i = 0;
|
||||||
sentences.Add(new Sentence("new_file_" + added_files.ToString() + ";New Description;New Voice Message", @"SOUNDS\" + curLang.sName + @"\"));
|
string newFile;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
newFile = "new_file_" + i.ToString();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
while (env.fileExists(newFile));
|
||||||
|
sentences.Add(new Sentence(newFile + ";New Description;New Voice Message", env.baseDir()));
|
||||||
this.lvSentences.Items.Refresh();
|
this.lvSentences.Items.Refresh();
|
||||||
this.lvSentences.SelectedIndex = this.lvSentences.Items.Count - 1;
|
this.lvSentences.SelectedIndex = this.lvSentences.Items.Count - 1;
|
||||||
this.lvSentences.ScrollIntoView(this.lvSentences.SelectedItem);
|
this.lvSentences.ScrollIntoView(this.lvSentences.SelectedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnRecord_Click(object sender, RoutedEventArgs e)
|
private void record(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (this.lvSentences.SelectedItems.Count < 1) return;
|
if (this.lvSentences.SelectedItems.Count < 1) return;
|
||||||
|
|
||||||
if (isRecording)
|
if (isRecording)
|
||||||
btnStop_Click(sender, e);
|
stop(sender, e);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
saveLanguage();
|
saveLanguage();
|
||||||
|
@ -164,30 +184,77 @@ namespace OpenTXrecorder
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path)); // Create directory if it doesn't exist
|
Directory.CreateDirectory(Path.GetDirectoryName(path)); // Create directory if it doesn't exist
|
||||||
System.IO.File.WriteAllText(path, ""); // Creates and flushes a file if it does not exist
|
System.IO.File.WriteAllText(path, ""); // Creates and flushes a file if it does not exist
|
||||||
|
|
||||||
mciSendString("open new Type waveaudio Alias recsound", "", 0, 0);
|
int samplerate = 16000;
|
||||||
mciSendString("record recsound", "", 0, 0);
|
int bits = 16; // 8 or 16
|
||||||
|
int channels = 1; // 1 or 2
|
||||||
|
|
||||||
|
filewriter = new WavFileWriter(path, samplerate, bits, channels);
|
||||||
|
WaveFormat fmt = new WaveFormat(samplerate, bits, channels);
|
||||||
|
|
||||||
|
// devicenumber, wavformat, buffersize, callback
|
||||||
|
int buffersize = 1024 * 2;
|
||||||
|
tmparray = new byte[buffersize];
|
||||||
|
recorder = new WaveInRecorder(-1, fmt, buffersize, this.DataArrived);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnStop_Click(object sender, RoutedEventArgs e)
|
private void stop(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (this.lvSentences.SelectedItems.Count < 1) return;
|
if (!isRecording || this.lvSentences.SelectedItems.Count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
isRecording = false;
|
||||||
|
recorder.Close();
|
||||||
|
filewriter.Close();
|
||||||
|
|
||||||
int index = this.lvSentences.SelectedIndex;
|
int index = this.lvSentences.SelectedIndex;
|
||||||
lblRecording.Visibility = System.Windows.Visibility.Hidden;
|
lblRecording.Visibility = System.Windows.Visibility.Hidden;
|
||||||
isRecording = false;
|
|
||||||
Sentence sentence = (Sentence)this.lvSentences.SelectedItem;
|
Sentence sentence = (Sentence)this.lvSentences.SelectedItem;
|
||||||
mciSendString("save recsound " + sentence.fullPath(), "", 0, 0);
|
|
||||||
mciSendString("close recsound ", "", 0, 0);
|
|
||||||
|
|
||||||
loadLanguage(); // Called to refresh the sentence data of the current langugae
|
loadLanguage(); // Called to refresh the sentence data of the current langugae
|
||||||
this.lvSentences.SelectedIndex = index;
|
this.lvSentences.SelectedIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnPlay_Click(object sender, RoutedEventArgs e)
|
private void DataArrived(IntPtr data, int size)
|
||||||
{
|
{
|
||||||
lblRecording.Visibility = System.Windows.Visibility.Hidden;
|
if (!isRecording)
|
||||||
lvSentences_MouseDoubleClick(sender, null);
|
return;
|
||||||
|
|
||||||
|
Marshal.Copy(data, tmparray, 0, size);
|
||||||
|
filewriter.Write(tmparray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Environment
|
||||||
|
{
|
||||||
|
public string shortLanguage { get; set; }
|
||||||
|
public string baseDir() { return @"SOUNDS\" + shortLanguage + @"\"; }
|
||||||
|
public string sysDir() { return @"SOUNDS\" + shortLanguage + @"\SYSTEM\"; }
|
||||||
|
public string otherSounds() { return baseDir() + "other_sounds.txt"; }
|
||||||
|
public string systemSounds() { return sysDir() + "system_sounds.txt"; }
|
||||||
|
|
||||||
|
public bool fileExists(string fName)
|
||||||
|
{
|
||||||
|
if (File.Exists(System.AppDomain.CurrentDomain.BaseDirectory + baseDir() + fName + ".wav"))
|
||||||
|
return true;
|
||||||
|
if (File.Exists(System.AppDomain.CurrentDomain.BaseDirectory + sysDir() + fName + ".wav"))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment(string str)
|
||||||
|
{
|
||||||
|
shortLanguage = str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Data container classes
|
||||||
|
*/
|
||||||
|
public class Languages : List<Language>
|
||||||
|
{
|
||||||
|
public void Add(string longer, string shorter)
|
||||||
|
{
|
||||||
|
this.Add(new Language { lName = longer, sName = shorter });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,21 +264,21 @@ namespace OpenTXrecorder
|
||||||
public string sName { get; set; }
|
public string sName { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Languages : List<Language>
|
public class Sentences : List<Sentence>
|
||||||
{
|
{
|
||||||
public void Add(string longer, string shorter)
|
public void Add(string rawString, string dirPath)
|
||||||
{
|
{
|
||||||
this.Add(new Language { lName = longer, sName = shorter });
|
this.Add(new Sentence(rawString, dirPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Sentence
|
public class Sentence
|
||||||
{
|
{
|
||||||
|
public bool fileExists { get; set; }
|
||||||
public string fileName { get; set; }
|
public string fileName { get; set; }
|
||||||
public string description { get; set; }
|
public string description { get; set; }
|
||||||
public string voiceString { get; set; }
|
public string voiceString { get; set; }
|
||||||
public string path { get; set; }
|
public string path { get; set; }
|
||||||
public bool fileExists { get; set; }
|
|
||||||
|
|
||||||
public Sentence(string rawString, string dirPath)
|
public Sentence(string rawString, string dirPath)
|
||||||
{
|
{
|
||||||
|
@ -234,11 +301,4 @@ namespace OpenTXrecorder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Sentences : List<Sentence>
|
|
||||||
{
|
|
||||||
public void Add(string rawString, string dirPath)
|
|
||||||
{
|
|
||||||
this.Add(new Sentence(rawString, dirPath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,8 @@
|
||||||
<DependentUpon>App.xaml</DependentUpon>
|
<DependentUpon>App.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="sentencetables.cs" />
|
<Compile Include="sentencetables.cs" />
|
||||||
|
<Compile Include="WaveIn.cs" />
|
||||||
|
<Compile Include="WaveNative.cs" />
|
||||||
<Page Include="MainWindow.xaml">
|
<Page Include="MainWindow.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
|
|
101
sound/recorder/Program.cs
Normal file
101
sound/recorder/Program.cs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using WaveLib;
|
||||||
|
|
||||||
|
namespace AudioRecorder
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
WavFileWriter filewriter;
|
||||||
|
byte[] tmparray;
|
||||||
|
|
||||||
|
void Go()
|
||||||
|
{
|
||||||
|
int samplerate = 16000;
|
||||||
|
int bits = 16; // 8 or 16
|
||||||
|
int channels = 1; // 1 or 2
|
||||||
|
|
||||||
|
filewriter = new WavFileWriter("out.wav", samplerate, bits, channels);
|
||||||
|
|
||||||
|
WaveFormat fmt = new WaveFormat(samplerate, bits, channels);
|
||||||
|
|
||||||
|
// devicenumber, wavformat, buffersize, callback
|
||||||
|
int buffersize = 16384;
|
||||||
|
WaveInRecorder rec = new WaveInRecorder(-1, fmt, buffersize, this.DataArrived);
|
||||||
|
tmparray = new byte[buffersize];
|
||||||
|
|
||||||
|
Console.WriteLine("Recording - press Enter to end");
|
||||||
|
Console.ReadLine();
|
||||||
|
rec.Close();
|
||||||
|
|
||||||
|
filewriter.Close();
|
||||||
|
|
||||||
|
Console.WriteLine("Bye");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DataArrived(IntPtr data, int size)
|
||||||
|
{
|
||||||
|
Console.WriteLine("DataArrived {0} bytes", size);
|
||||||
|
Marshal.Copy(data, tmparray, 0, size);
|
||||||
|
filewriter.Write(tmparray);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
new Program().Go();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WavFileWriter
|
||||||
|
{
|
||||||
|
BinaryWriter filewriter;
|
||||||
|
long audiobyteswritten;
|
||||||
|
|
||||||
|
public WavFileWriter(string filename, int samplerate, int bits, int channels)
|
||||||
|
{
|
||||||
|
filewriter = new BinaryWriter(File.Open(filename, FileMode.Create));
|
||||||
|
|
||||||
|
filewriter.Write((uint)0x46464952); // "RIFF"
|
||||||
|
filewriter.Write((uint)0); // chunkisize (filled in when we close)
|
||||||
|
filewriter.Write((uint)0x45564157); // "WAVE"
|
||||||
|
|
||||||
|
// subchunk1
|
||||||
|
filewriter.Write((uint)0x20746d66); // "fmt "
|
||||||
|
filewriter.Write((uint)16);
|
||||||
|
filewriter.Write((UInt16)1);
|
||||||
|
filewriter.Write((UInt16)channels);
|
||||||
|
filewriter.Write((uint)samplerate);
|
||||||
|
filewriter.Write((uint)(channels*samplerate*bits/8));
|
||||||
|
filewriter.Write((UInt16)(channels*bits/8));
|
||||||
|
filewriter.Write((UInt16)bits);
|
||||||
|
|
||||||
|
// subchunk2
|
||||||
|
filewriter.Write((uint)0x61746164); // "data"
|
||||||
|
filewriter.Write((uint)0); // chunkisize (filled in when we close)
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(byte[] data)
|
||||||
|
{
|
||||||
|
filewriter.Write(data);
|
||||||
|
audiobyteswritten += data.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
filewriter.Seek(4, SeekOrigin.Begin);
|
||||||
|
filewriter.Write((uint)(audiobyteswritten + 36)); // 36 = total size of chunk headers
|
||||||
|
|
||||||
|
filewriter.Seek(40, SeekOrigin.Begin);
|
||||||
|
filewriter.Write((uint)audiobyteswritten);
|
||||||
|
|
||||||
|
filewriter.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Recorder. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
214
sound/recorder/WaveIn.cs
Normal file
214
sound/recorder/WaveIn.cs
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
|
||||||
|
* This code is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with the code. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Copyright 2014 Tomas Andersson */
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace WaveLib
|
||||||
|
{
|
||||||
|
public delegate void BufferDoneEventHandler(IntPtr data, int size);
|
||||||
|
|
||||||
|
internal class WaveInBuffer
|
||||||
|
{
|
||||||
|
private ManualResetEvent m_RecordEvent = new ManualResetEvent(false);
|
||||||
|
private IntPtr m_WaveIn;
|
||||||
|
|
||||||
|
private WaveNative.WaveHdr m_Header;
|
||||||
|
private byte[] m_HeaderData;
|
||||||
|
private GCHandle m_HeaderHandle;
|
||||||
|
private GCHandle m_HeaderDataHandle;
|
||||||
|
|
||||||
|
internal static void WaveInProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
|
||||||
|
{
|
||||||
|
if (uMsg == WaveNative.MM_WIM_DATA)
|
||||||
|
{
|
||||||
|
GCHandle h = (GCHandle)wavhdr.dwUser;
|
||||||
|
WaveInBuffer buf = (WaveInBuffer)h.Target;
|
||||||
|
buf.OnCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WaveInBuffer(IntPtr waveInHandle, int size)
|
||||||
|
{
|
||||||
|
m_WaveIn = waveInHandle;
|
||||||
|
|
||||||
|
m_HeaderHandle = GCHandle.Alloc(m_Header, GCHandleType.Pinned);
|
||||||
|
m_Header.dwUser = (IntPtr)GCHandle.Alloc(this);
|
||||||
|
m_HeaderData = new byte[size];
|
||||||
|
m_HeaderDataHandle = GCHandle.Alloc(m_HeaderData, GCHandleType.Pinned);
|
||||||
|
m_Header.lpData = m_HeaderDataHandle.AddrOfPinnedObject();
|
||||||
|
m_Header.dwBufferLength = size;
|
||||||
|
WaveInRecorder.ThrowOnError(WaveNative.waveInPrepareHeader(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
WaveInRecorder.ThrowOnError(WaveNative.waveInUnprepareHeader(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header)));
|
||||||
|
m_HeaderHandle.Free();
|
||||||
|
m_Header.lpData = IntPtr.Zero;
|
||||||
|
m_HeaderDataHandle.Free();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Size
|
||||||
|
{
|
||||||
|
get { return m_Header.dwBufferLength; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr Data
|
||||||
|
{
|
||||||
|
get { return m_Header.lpData; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Record()
|
||||||
|
{
|
||||||
|
m_RecordEvent.Reset();
|
||||||
|
WaveInRecorder.ThrowOnError(WaveNative.waveInAddBuffer(m_WaveIn, ref m_Header, Marshal.SizeOf(m_Header)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitFor()
|
||||||
|
{
|
||||||
|
m_RecordEvent.WaitOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCompleted()
|
||||||
|
{
|
||||||
|
m_RecordEvent.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WavFileWriter
|
||||||
|
{
|
||||||
|
BinaryWriter filewriter;
|
||||||
|
long audiobyteswritten;
|
||||||
|
|
||||||
|
public WavFileWriter(string filename, int samplerate, int bits, int channels)
|
||||||
|
{
|
||||||
|
filewriter = new BinaryWriter(File.Open(filename, FileMode.Create));
|
||||||
|
|
||||||
|
filewriter.Write((uint)0x46464952); // "RIFF"
|
||||||
|
filewriter.Write((uint)0); // chunkisize (filled in when we close)
|
||||||
|
filewriter.Write((uint)0x45564157); // "WAVE"
|
||||||
|
|
||||||
|
// subchunk1
|
||||||
|
filewriter.Write((uint)0x20746d66); // "fmt "
|
||||||
|
filewriter.Write((uint)16);
|
||||||
|
filewriter.Write((UInt16)1);
|
||||||
|
filewriter.Write((UInt16)channels);
|
||||||
|
filewriter.Write((uint)samplerate);
|
||||||
|
filewriter.Write((uint)(channels * samplerate * bits / 8));
|
||||||
|
filewriter.Write((UInt16)(channels * bits / 8));
|
||||||
|
filewriter.Write((UInt16)bits);
|
||||||
|
|
||||||
|
// subchunk2
|
||||||
|
filewriter.Write((uint)0x61746164); // "data"
|
||||||
|
filewriter.Write((uint)0); // chunkisize (filled in when we close)
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(byte[] data)
|
||||||
|
{
|
||||||
|
filewriter.Write(data);
|
||||||
|
audiobyteswritten += data.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
filewriter.Seek(4, SeekOrigin.Begin);
|
||||||
|
filewriter.Write((uint)(audiobyteswritten + 36)); // 36 = total size of chunk headers
|
||||||
|
|
||||||
|
filewriter.Seek(40, SeekOrigin.Begin);
|
||||||
|
filewriter.Write((uint)audiobyteswritten);
|
||||||
|
|
||||||
|
filewriter.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WaveInRecorder
|
||||||
|
{
|
||||||
|
private IntPtr m_WaveIn;
|
||||||
|
private WaveInBuffer buffer1, buffer2;
|
||||||
|
private WaveInBuffer m_CurrentBuffer;
|
||||||
|
private Thread m_Thread;
|
||||||
|
private BufferDoneEventHandler m_DoneProc;
|
||||||
|
private bool m_Finished;
|
||||||
|
|
||||||
|
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveInBuffer.WaveInProc);
|
||||||
|
|
||||||
|
public static int DeviceCount
|
||||||
|
{
|
||||||
|
get { return WaveNative.waveInGetNumDevs(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public WaveInRecorder(int device, WaveFormat format, int bufferSize, BufferDoneEventHandler doneProc)
|
||||||
|
{
|
||||||
|
m_DoneProc = doneProc;
|
||||||
|
WaveInRecorder.ThrowOnError(WaveNative.waveInOpen(out m_WaveIn, device, format, m_BufferProc, 0, WaveNative.CALLBACK_FUNCTION));
|
||||||
|
|
||||||
|
buffer1 = new WaveInBuffer(m_WaveIn, bufferSize);
|
||||||
|
buffer2 = new WaveInBuffer(m_WaveIn, bufferSize);
|
||||||
|
|
||||||
|
buffer1.Record();
|
||||||
|
buffer2.Record();
|
||||||
|
|
||||||
|
m_CurrentBuffer = buffer1;
|
||||||
|
|
||||||
|
WaveInRecorder.ThrowOnError(WaveNative.waveInStart(m_WaveIn));
|
||||||
|
m_Thread = new Thread(new ThreadStart(ThreadProc));
|
||||||
|
m_Thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
m_Finished = true;
|
||||||
|
WaveInRecorder.ThrowOnError(WaveNative.waveInReset(m_WaveIn));
|
||||||
|
|
||||||
|
m_Thread.Join();
|
||||||
|
m_Thread = null;
|
||||||
|
m_DoneProc = null;
|
||||||
|
|
||||||
|
buffer1.WaitFor();
|
||||||
|
buffer2.WaitFor();
|
||||||
|
|
||||||
|
buffer1.Close();
|
||||||
|
buffer2.Close();
|
||||||
|
|
||||||
|
WaveInRecorder.ThrowOnError(WaveNative.waveInClose(m_WaveIn));
|
||||||
|
m_WaveIn = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThreadProc()
|
||||||
|
{
|
||||||
|
while (!m_Finished)
|
||||||
|
{
|
||||||
|
m_CurrentBuffer = (m_CurrentBuffer == buffer1) ? buffer2 : buffer1;
|
||||||
|
|
||||||
|
m_CurrentBuffer.WaitFor();
|
||||||
|
m_DoneProc(m_CurrentBuffer.Data, m_CurrentBuffer.Size);
|
||||||
|
|
||||||
|
if (m_Finished) break;
|
||||||
|
|
||||||
|
m_CurrentBuffer.Record();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ThrowOnError(int err)
|
||||||
|
{
|
||||||
|
if (err != WaveNative.MMSYSERR_NOERROR) throw new Exception(err.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
129
sound/recorder/WaveNative.cs
Normal file
129
sound/recorder/WaveNative.cs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
|
||||||
|
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE.
|
||||||
|
//
|
||||||
|
// Email: ianier@hotmail.com
|
||||||
|
//
|
||||||
|
// Copyright (C) 1999-2003 Ianier Munoz. All Rights Reserved.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace WaveLib
|
||||||
|
{
|
||||||
|
public enum WaveFormats
|
||||||
|
{
|
||||||
|
Pcm = 1,
|
||||||
|
Float = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public class WaveFormat
|
||||||
|
{
|
||||||
|
public short wFormatTag;
|
||||||
|
public short nChannels;
|
||||||
|
public int nSamplesPerSec;
|
||||||
|
public int nAvgBytesPerSec;
|
||||||
|
public short nBlockAlign;
|
||||||
|
public short wBitsPerSample;
|
||||||
|
public short cbSize;
|
||||||
|
|
||||||
|
public WaveFormat(int rate, int bits, int channels)
|
||||||
|
{
|
||||||
|
wFormatTag = (short)WaveFormats.Pcm;
|
||||||
|
nChannels = (short)channels;
|
||||||
|
nSamplesPerSec = rate;
|
||||||
|
wBitsPerSample = (short)bits;
|
||||||
|
cbSize = 0;
|
||||||
|
|
||||||
|
nBlockAlign = (short)(channels * (bits / 8));
|
||||||
|
nAvgBytesPerSec = nSamplesPerSec * nBlockAlign;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class WaveNative
|
||||||
|
{
|
||||||
|
// consts
|
||||||
|
public const int MMSYSERR_NOERROR = 0; // no error
|
||||||
|
|
||||||
|
public const int MM_WOM_OPEN = 0x3BB;
|
||||||
|
public const int MM_WOM_CLOSE = 0x3BC;
|
||||||
|
public const int MM_WOM_DONE = 0x3BD;
|
||||||
|
|
||||||
|
public const int MM_WIM_OPEN = 0x3BE;
|
||||||
|
public const int MM_WIM_CLOSE = 0x3BF;
|
||||||
|
public const int MM_WIM_DATA = 0x3C0;
|
||||||
|
|
||||||
|
public const int CALLBACK_FUNCTION = 0x00030000; // dwCallback is a FARPROC
|
||||||
|
|
||||||
|
public const int TIME_MS = 0x0001; // time in milliseconds
|
||||||
|
public const int TIME_SAMPLES = 0x0002; // number of wave samples
|
||||||
|
public const int TIME_BYTES = 0x0004; // current byte offset
|
||||||
|
|
||||||
|
// callbacks
|
||||||
|
public delegate void WaveDelegate(IntPtr hdrvr, int uMsg, int dwUser, ref WaveHdr wavhdr, int dwParam2);
|
||||||
|
|
||||||
|
// structs
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)] public struct WaveHdr
|
||||||
|
{
|
||||||
|
public IntPtr lpData; // pointer to locked data buffer
|
||||||
|
public int dwBufferLength; // length of data buffer
|
||||||
|
public int dwBytesRecorded; // used for input only
|
||||||
|
public IntPtr dwUser; // for client's use
|
||||||
|
public int dwFlags; // assorted flags (see defines)
|
||||||
|
public int dwLoops; // loop control counter
|
||||||
|
public IntPtr lpNext; // PWaveHdr, reserved for driver
|
||||||
|
public int reserved; // reserved for driver
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string mmdll = "winmm.dll";
|
||||||
|
|
||||||
|
// WaveOut calls
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutGetNumDevs();
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutPrepareHeader(IntPtr hWaveOut, ref WaveHdr lpWaveOutHdr, int uSize);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutUnprepareHeader(IntPtr hWaveOut, ref WaveHdr lpWaveOutHdr, int uSize);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutWrite(IntPtr hWaveOut, ref WaveHdr lpWaveOutHdr, int uSize);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutOpen(out IntPtr hWaveOut, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutReset(IntPtr hWaveOut);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutClose(IntPtr hWaveOut);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutPause(IntPtr hWaveOut);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutRestart(IntPtr hWaveOut);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutGetPosition(IntPtr hWaveOut, out int lpInfo, int uSize);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutSetVolume(IntPtr hWaveOut, int dwVolume);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveOutGetVolume(IntPtr hWaveOut, out int dwVolume);
|
||||||
|
|
||||||
|
// WaveIn calls
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveInGetNumDevs();
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveInAddBuffer(IntPtr hwi, ref WaveHdr pwh, int cbwh);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveInClose(IntPtr hwi);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveInOpen(out IntPtr phwi, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveInPrepareHeader(IntPtr hWaveIn, ref WaveHdr lpWaveInHdr, int uSize);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveInUnprepareHeader(IntPtr hWaveIn, ref WaveHdr lpWaveInHdr, int uSize);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveInReset(IntPtr hwi);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveInStart(IntPtr hwi);
|
||||||
|
[DllImport(mmdll)]
|
||||||
|
public static extern int waveInStop(IntPtr hwi);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Recorder. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Recorder. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Speaker. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Speaker. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Speaker. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Speaker. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Speaker. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
|
* along with OpenTX Speaker. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2014 Kjell Kernen */
|
* Copyright 2014 Kjell Kernen */
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue