Skip to content

Commit 1f0dcde

Browse files
author
Bart Lauret
committed
Import SigGen 1.0
0 parents  commit 1f0dcde

File tree

6 files changed

+402
-0
lines changed

6 files changed

+402
-0
lines changed

Plugin.pm

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package Plugins::SigGen::Plugin;
2+
3+
# Signal Generator Plugin
4+
5+
use strict;
6+
7+
use base qw(Slim::Plugin::Base);
8+
9+
use Slim::Utils::Strings qw(string);
10+
use Slim::Utils::Prefs;
11+
12+
use Plugins::SigGen::ProtocolHandler;
13+
14+
sub getDisplayName { 'PLUGIN_SIGGEN' };
15+
16+
sub modeName { 'PLUGIN_SIGGEN' };
17+
18+
my $prefs = preferences('server');
19+
20+
# following are the setting which are changed from the remote with button presses
21+
my @funcs = ( 'sine', 'square', 'triangle', 'sawtooth' ); # button 1
22+
my @amps = ( -80, -60, -40, -20, -10, -6, 0 ); # button 2 inc, button 5 dec
23+
my @chans = ( 'l+r', 'l', 'r' ); # button 3
24+
my @bits = ( 16, 24 ); # button 4
25+
my @rates = ( 8, 11, 12, 16, 22, 24, 32, 44, 48, 88, 96 ); # button 6
26+
27+
sub setMode {
28+
my $class = shift;
29+
my $client = shift;
30+
31+
my $s = $client->modeParam('params') || {
32+
'freq' => 500,
33+
'func' => 0,
34+
'chan' => 0,
35+
'amp' => 2,
36+
'bits' => 0,
37+
'rate' => 7,
38+
};
39+
40+
$client->modeParam('params', $s);
41+
42+
$client->lines(\&lines);
43+
44+
Slim::Utils::Timers::setTimer($client, Time::HiRes::time + 1.0, \&update);
45+
46+
avoidScreenSaver($client);
47+
}
48+
49+
sub exitMode {
50+
my $class = shift;
51+
my $client = shift;
52+
53+
$client->execute(['playlist', 'clear']);
54+
55+
for my $track (Slim::Schema->rs('Track')->search_like({ 'url' => 'siggen%' })->all) {
56+
$track->delete;
57+
}
58+
59+
$client->display->updateMode(0);
60+
}
61+
62+
sub lines {
63+
my $client = shift;
64+
65+
my $s = $client->modeParam('params');
66+
67+
return {
68+
'line' => [ string('PLUGIN_SIGGEN'), string('PLUGIN_SIGGEN_' . uc $funcs[$s->{'func'}]) ],
69+
'overlay' => [
70+
(sprintf "%s %s/%s", string('PLUGIN_SIGGEN_' . uc $chans[$s->{'chan'}]), $rates[$s->{'rate'}], $bits[$s->{'bits'}]),
71+
(sprintf "%s dB %s Hz", $amps[$s->{'amp'}], $s->{'freq'}),
72+
],
73+
};
74+
}
75+
76+
my %functions = (
77+
'left' => sub { Slim::Buttons::Common::popModeRight(shift) },
78+
79+
'right'=> sub { shift->bumpRight },
80+
81+
'up' => sub {
82+
my $client = shift;
83+
my $s = $client->modeParam('params');
84+
85+
if ($s->{'freq'} < 20000) {
86+
87+
$s->{'freq'} += ($s->{'freq'} >= 100 ? 100 : 10);
88+
89+
update($client);
90+
}
91+
},
92+
93+
'down' => sub {
94+
my $client = shift;
95+
my $s = $client->modeParam('params');
96+
97+
if ($s->{'freq'} > 10) {
98+
99+
$s->{'freq'} -= ($s->{'freq'} > 100 ? 100 : 10);
100+
101+
update($client);
102+
}
103+
},
104+
105+
'jump' => sub {
106+
my ($client, $funct, $arg) = @_;
107+
my $s = $client->modeParam('params');
108+
109+
if ($arg eq 'rew') {
110+
$s->{'freq'} = $s->{'freq'} > 100 ? $s->{'freq'} / 10 : $s->{'freq'};
111+
}
112+
113+
if ($arg eq 'fwd') {
114+
$s->{'freq'} = $s->{'freq'} <= 2000 ? $s->{'freq'} * 10 : $s->{'freq'};
115+
}
116+
117+
update($client);
118+
},
119+
120+
'numberScroll' => sub {
121+
my ($client, $funct, $arg) = @_;
122+
123+
my $s = $client->modeParam('params');
124+
125+
if ($arg eq '1') {
126+
$s->{'func'} = ($s->{'func'} + 1) % scalar @funcs;
127+
}
128+
129+
if ($arg eq '2') {
130+
$s->{'amp'} = $s->{'amp'} < $#amps ? $s->{'amp'} + 1 : $s->{'amp'};
131+
}
132+
133+
if ($arg eq '3') {
134+
$s->{'chan'} = ($s->{'chan'} + 1) % scalar @chans;
135+
}
136+
137+
if ($arg eq '4') {
138+
$s->{'rate'} = ($s->{'rate'} + 1) % scalar @rates;
139+
if ($rates[$s->{'rate'}] * 1000 > $client->maxSupportedSamplerate) {
140+
$s->{'rate'} = 0;
141+
}
142+
}
143+
144+
if ($arg eq '5') {
145+
$s->{'amp'} = $s->{'amp'} > 0 ? $s->{'amp'} - 1 : 0;
146+
}
147+
148+
if ($arg eq '6') {
149+
$s->{'bits'} = ($s->{'bits'} + 1) % scalar @bits;
150+
}
151+
152+
update($client);
153+
}
154+
);
155+
156+
sub getFunctions {
157+
return \%functions;
158+
}
159+
160+
sub update {
161+
my $client = shift;
162+
163+
my $s = $client->modeParam('params');
164+
165+
my $freq = $s->{'freq'};
166+
my $func = $funcs[$s->{'func'}];
167+
my $rate = $rates[$s->{'rate'}];
168+
my $bits = $bits[$s->{'bits'}];
169+
my $ampL = ($chans[$s->{'chan'}] =~ /l/) ? $amps[$s->{'amp'}] : 'off';
170+
my $ampR = ($chans[$s->{'chan'}] =~ /r/) ? $amps[$s->{'amp'}] : 'off';
171+
172+
my $url = "siggen://test.raw?func=$func&freq=$freq&rate=$rate&bits=$bits&ampL=$ampL&ampR=$ampR";
173+
174+
Slim::Music::Info::setTitle($url, string('PLUGIN_SIGGEN_TESTSIGNAL') ." $func $freq Hz");
175+
176+
$client->display->updateMode(1);
177+
$client->showBriefly(lines($client), { 'duration' => 3, 'block' => 1 });
178+
179+
Slim::Utils::Timers::killTimers($client, \&execute);
180+
Slim::Utils::Timers::setTimer($client, Time::HiRes::time() + 0.5, \&execute, $url);
181+
}
182+
183+
sub execute {
184+
my $client = shift;
185+
my $url = shift;
186+
187+
$client->execute(['playlist', 'play', $url]);
188+
}
189+
190+
sub avoidScreenSaver {
191+
my $client = shift;
192+
193+
my $now = Time::HiRes::time();
194+
my $timeout = $prefs->client($client)->get('screensavertimeout') || return;
195+
196+
if (Slim::Buttons::Common::mode($client) eq 'PLUGIN_SIGGEN') {
197+
198+
if ($now - Slim::Hardware::IR::lastIRTime($client) > $timeout / 2) {
199+
Slim::Hardware::IR::setLastIRTime($client, $now);
200+
}
201+
202+
Slim::Utils::Timers::setTimer($client, $now + $timeout / 2, \&avoidScreenSaver);
203+
}
204+
}
205+
206+
1;

ProtocolHandler.pm

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package Plugins::SigGen::ProtocolHandler;
2+
3+
use strict;
4+
5+
use base qw(IO::Handle);
6+
7+
use Slim::Utils::Log;
8+
9+
my $log = Slim::Utils::Log->addLogCategory({
10+
'category' => 'plugin.siggen',
11+
'defaultLevel' => 'WARN',
12+
'description' => 'PLUGIN_SIGGEN',
13+
});
14+
15+
use bytes;
16+
17+
my $rates = {
18+
'8' => 8000,
19+
'11' => 11025,
20+
'12' => 12000,
21+
'16' => 16000,
22+
'22' => 22050,
23+
'32' => 32000,
24+
'44' => 44100,
25+
'48' => 48000,
26+
'88' => 88200,
27+
'96' => 96000,
28+
};
29+
30+
use constant TWO_PI => 8 * atan2(1, 1);
31+
32+
# following define the functions that we can can generate signals for
33+
# called with $_[0] = sample number, $_[1] = period in samples
34+
my $functions = {
35+
'sine' => sub { sin( TWO_PI * ($_[0] % $_[1]) / $_[1] ) },
36+
'square' => sub { ($_[0] % $_[1] >= $_[1] / 2) ? 1 : -1 },
37+
'triangle' => sub { ($_[0] % $_[1] >= $_[1] / 2) ? 3 - 4 * ($_[0] % $_[1]) / $_[1] : -1 + 4 * ($_[0] % $_[1]) / $_[1] },
38+
'sawtooth' => sub { 2 * ($_[0] % $_[1]) / $_[1] - 1 },
39+
'silence' => sub { 0 },
40+
};
41+
42+
Slim::Player::ProtocolHandlers->registerHandler('siggen', __PACKAGE__);
43+
44+
# accept urls of the form: siggen://test.raw?func=sine&freq=1000&rate=96&bits=24&ampL=0&ampR=0
45+
46+
sub new {
47+
my $class = shift;
48+
my $args = shift;
49+
50+
my $self = $class->SUPER::new;
51+
52+
my $url = $args->{'url'};
53+
my $client = $args->{'client'};
54+
55+
unless ($url =~ /siggen:\/\/test.raw/) {
56+
$log->warn("bad url: $url");
57+
return undef;
58+
}
59+
60+
$log->info("url: $url");
61+
62+
my ($query) = $url =~ /\?(.*)/;
63+
64+
my $params = {};
65+
66+
for my $param (split /\&/, $query) {
67+
my ($key, $value) = $param =~ /(.*)=(.*)/;
68+
$params->{ $key } = $value;
69+
}
70+
71+
my $func = $functions->{ $params->{'func'} || 'silence' };
72+
my $freq = $params->{'freq'} || 1000;
73+
my $rate = $rates->{ $params->{'rate'} } || 44100;
74+
my $bits = $params->{'bits'} && $params->{'bits'} == 24 ? 24 : 16;
75+
76+
my $max = $bits == 16 ? 0x7fff : 0x7fffff;
77+
78+
my $ampL = !exists $params->{'ampL'} || $params->{'ampL'} eq 'off' ? 0 : exp( $params->{'ampL'} / 20 * log(10) ) * $max;
79+
my $ampR = !exists $params->{'ampR'} || $params->{'ampR'} eq 'off' ? 0 : exp( $params->{'ampR'} / 20 * log(10) ) * $max;
80+
81+
$log->info("freq: $freq function: $params->{'func'} $rate/$bits left: $ampL right: $ampR");
82+
83+
my $period = int (($rate / $freq) + 0.5);
84+
85+
$log->info("period: $period actual freq: " . $rate / $period . " Hz");
86+
87+
# length of buffer is an integral number of sample periods to avoid joins when it is repeated
88+
my $samples = int(Slim::Web::HTTP::MAXCHUNKSIZE / ($bits/4)) - int(Slim::Web::HTTP::MAXCHUNKSIZE / ($bits/4)) % $period;
89+
90+
my $buf = '';
91+
92+
for (my $s = 0; $s < $samples; $s++) {
93+
94+
my $val = &$func($s, $period);
95+
96+
if ($bits == 16) {
97+
# 16 bits
98+
$buf .= pack "ss", $ampL * $val, $ampR * $val;
99+
100+
} else {
101+
# 24 bits
102+
my $valL = ($val * $ampL) & 0xffffff;
103+
my $valR = ($val * $ampR) & 0xffffff;
104+
105+
$buf .=
106+
chr ($valL & 0xff) . chr (($valL >> 8) & 0xff) . chr (($valL >> 16) & 0xff) .
107+
chr ($valR & 0xff) . chr (($valR >> 8) & 0xff) . chr (($valR >> 16) & 0xff) ;
108+
}
109+
}
110+
111+
$log->debug("created buffer containing $samples samples, length: " . length $buf);
112+
113+
${*$self}{'buf'} = $buf;
114+
115+
# we need to store the sample rate and size in the database
116+
my $track = Slim::Schema->rs('Track')->objectForUrl({
117+
'url' => $url,
118+
});
119+
120+
$track->samplesize($bits);
121+
$track->samplerate($rate);
122+
$track->update;
123+
124+
return $self;
125+
}
126+
127+
# this handles streaming of the buffer to the player - just send the whole buffer each time
128+
sub sysread {
129+
my $self = $_[0];
130+
131+
$_[1] = ${*$self}{'buf'};
132+
133+
return length $_[1];
134+
}
135+
136+
sub isAudioURL { 1 }
137+
138+
sub isRemote { 0 }
139+
140+
sub contentType { 'raw' }
141+
142+
1;

custom-convert.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# SigGen convert - used to avoid processing test signal by flac
2+
3+
raw wav * *
4+
-

custom-types.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SigGen types
2+
raw siggen: audio/raw audio

install.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0"?>
2+
3+
<extension>
4+
<name>PLUGIN_SIGGEN</name>
5+
<module>Plugins::SigGen::Plugin</module>
6+
<version>1.0</version>
7+
<description>PLUGIN_SIGGEN_DESC</description>
8+
<defaultState>enabled</defaultState>
9+
<targetApplication>
10+
<minVersion>7.3</minVersion>
11+
<maxVersion>*</maxVersion>
12+
</targetApplication>
13+
</extension>

0 commit comments

Comments
 (0)